<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="keywords" content="玖忆;文鹤;博客">
    
    <meta name="author" content="meteor">
    <!-- preconnect -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    
    
    <!--- Seo Part-->
    
    <link rel="canonical" href="https://wait-you.github.io/2023/06/06/kotlin协程/"/>
    <meta name="robots" content="index,follow">
    <meta name="googlebot" content="index,follow">
    <meta name="revisit-after" content="1 days">
    
        <meta name="description" content="# Kotlin 协程 # 一、Kotlin 协程 Kotlin 协程 提供了一种全新处理并发的方式，你可以在 Android 平台上使用它来简化异步执行的代码。协程从 Kotlin 1.3 版本开始引入，但这一概念在编程世界诞生的黎明之际就有了，最早使用协程的编程语言可以追溯到 1967 年的 Simula  语言。在过去几年间，协程这个概念发展势头迅猛，现已经被诸多主流编程语言采用，比如 Ja">
<meta property="og:type" content="article">
<meta property="og:title" content="Kotlin协程">
<meta property="og:url" content="https://wait-you.github.io/2023/06/06/Kotlin%E5%8D%8F%E7%A8%8B/index.html">
<meta property="og:site_name" content="玖忆">
<meta property="og:description" content="# Kotlin 协程 # 一、Kotlin 协程 Kotlin 协程 提供了一种全新处理并发的方式，你可以在 Android 平台上使用它来简化异步执行的代码。协程从 Kotlin 1.3 版本开始引入，但这一概念在编程世界诞生的黎明之际就有了，最早使用协程的编程语言可以追溯到 1967 年的 Simula  语言。在过去几年间，协程这个概念发展势头迅猛，现已经被诸多主流编程语言采用，比如 Ja">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2023-06-06T00:53:30.000Z">
<meta property="article:modified_time" content="2023-06-05T08:53:56.480Z">
<meta property="article:author" content="meteor">
<meta property="article:tag" content="协程">
<meta name="twitter:card" content="summary">
    
    
    <!--- Icon Part-->
    <link rel="icon" type="image/png" href="/images/wenhe.png" sizes="192x192">
    <link rel="apple-touch-icon" sizes="180x180" href="/images/wenhe.png">
    <meta name="theme-color" content="#f1404b">
    <link rel="shortcut icon" href="/images/wenhe.png">
    <!--- Page Info-->
    
    <title>
        
            Kotlin协程 -
        
        玖忆
    </title>
    
<link rel="stylesheet" href="/css/style.css">

    
<link rel="stylesheet" href="/assets/fonts.css">

    <!--- Font Part-->
    
    
    
    

    <!--- Inject Part-->
    
    <script id="hexo-configurations">
    let Global = window.Global || {};
    Global.hexo_config = {"hostname":"wait-you.github.io","root":"/","language":"zh-CN"};
    Global.theme_config = {"articles":{"style":{"font_size":"16px","line_height":1.5,"image_border_radius":"14px","image_alignment":"center","image_caption":false,"link_icon":true},"word_count":{"enable":true,"count":true,"min2read":true},"author_label":{"enable":true,"auto":false,"list":[]},"code_block":{"copy":true,"style":"mac","font":{"enable":false,"family":null,"url":null}},"toc":{"enable":true,"max_depth":3,"number":false,"expand":true,"init_open":true},"copyright":true,"lazyload":true,"recommendation":{"enable":false,"title":"推荐阅读","limit":3,"placeholder":"http://tuchuang.wenhe9.cn/default-bg.jpg","skip_dirs":[]}},"colors":{"primary":"#f1404b","secondary":null},"global":{"fonts":{"chinese":{"enable":false,"family":null,"url":null},"english":{"enable":false,"family":null,"url":null}},"content_max_width":"1000px","sidebar_width":"210px","hover":{"shadow":true,"scale":false},"scroll_progress":{"bar":true,"percentage":true},"busuanzi_counter":{"enable":true,"site_pv":true,"site_uv":true,"post_pv":true},"pjax":true,"open_graph":true,"google_analytics":{"enable":false,"id":null}},"home_banner":{"enable":true,"style":"static","image":{"light":"http://tuchuang.wenhe9.cn/default-bg.jpg","dark":"http://tuchuang.wenhe9.cn/default-bg.jpg"},"title":"玖忆","subtitle":{"text":["我本微末凡尘、可也心向天空"],"hitokoto":{"enable":false,"api":"https://v1.hitokoto.cn"},"typing_speed":100,"backing_speed":80,"starting_delay":500,"backing_delay":1500,"loop":true,"smart_backspace":true},"text_color":{"light":"#fff","dark":"#d1d1b6"},"text_style":{"title_size":"2.8rem","subtitle_size":"1.5rem","line_height":1.2},"custom_font":{"enable":false,"family":null,"url":null},"social_links":{"enable":true,"links":{"github":"https://gitee.com/du-jinliang","instagram":null,"zhihu":null,"twitter":null,"email":"dujinliang9@163.com"}}},"plugins":{"feed":{"enable":false},"aplayer":{"enable":false,"type":"fixed","audios":[{"name":null,"artist":null,"url":null,"cover":null}]},"mermaid":{"enable":false,"version":"9.3.0"}},"version":"2.1.4","navbar":{"auto_hide":true,"color":{"left":"#f78736","right":"#367df7","transparency":35},"links":{"Home":{"path":"/","icon":"fa-regular fa-house"}},"search":{"enable":false,"preload":true}},"page_templates":{"friends_column":2,"tags_style":"blur"},"home":{"sidebar":{"enable":true,"position":"left","first_item":"menu","announcement":null,"links":null},"article_date_format":"auto","categories":{"enable":true,"limit":3},"tags":{"enable":true,"limit":3}}};
    Global.language_ago = {"second":"%s 秒前","minute":"%s 分钟前","hour":"%s 小时前","day":"%s 天前","week":"%s 周前","month":"%s 个月前","year":"%s 年前"};
    Global.data_config = {"masonry":false};
  </script>
    
    <!--- Fontawesome Part-->
    
<link rel="stylesheet" href="/fontawesome/fontawesome.min.css">

    
<link rel="stylesheet" href="/fontawesome/brands.min.css">

    
<link rel="stylesheet" href="/fontawesome/solid.min.css">

    
<link rel="stylesheet" href="/fontawesome/regular.min.css">

    
    
    
    
<meta name="generator" content="Hexo 6.3.0"></head>


<body>
<div class="progress-bar-container">
    
        <span class="scroll-progress-bar"></span>
    

    
        <span class="pjax-progress-bar"></span>
        <span class="pjax-progress-icon">
            <i class="fa-solid fa-circle-notch fa-spin"></i>
        </span>
    
</div>


<main class="page-container">

    

    <div class="main-content-container">

        <div class="main-content-header">
            <header class="navbar-container">
    
    <div class="navbar-content">
        <div class="left">
            
            <a class="logo-title" href="/">
                
                玖忆
                
            </a>
        </div>

        <div class="right">
            <!-- PC -->
            <div class="desktop">
                <ul class="navbar-list">
                    
                        
                            <li class="navbar-item">
                                <!-- Menu -->
                                <a class="" 
                                    href="/"  >
                                    
                                        
                                            <i class="fa-regular fa-house"></i>
                                        
                                        首页
                                    
                                </a>
                                <!-- Submenu -->
                                
                            </li>
                    
                    
                </ul>
            </div>
            <!-- Mobile -->
            <div class="mobile">
                
                <div class="icon-item navbar-bar">
                    <div class="navbar-bar-middle"></div>
                </div>
            </div>
        </div>
    </div>

    <!-- Mobile drawer -->
    <div class="navbar-drawer">
        <ul class="drawer-navbar-list">
            
                
                    <li class="drawer-navbar-item flex-center">
                        <a class="" 
                        href="/"  >
                             
                                
                                    <i class="fa-regular fa-house"></i>
                                
                                首页
                            
                        </a>
                    </li>
                    <!-- Submenu -->
                    
            

        </ul>
    </div>

    <div class="window-mask"></div>

</header>


        </div>

        <div class="main-content-body">

            

            <div class="main-content">

                
                    <div class="fade-in-down-animation">
    <div class="post-page-container">
        <div class="article-content-container">

            
            
                <div class="article-title">
                    <h1 class="article-title-regular">Kotlin协程</h1>
                </div>
            
                
            

            
                <div class="article-header">
                    <div class="avatar">
                        <img src="/images/wenhe.png">
                    </div>
                    <div class="info">
                        <div class="author">
                            <span class="name">meteor</span>
                            
                                <span class="author-label">Lv3</span>
                            
                        </div>
                        <div class="meta-info">
                            <div class="article-meta-info">
    <span class="article-date article-meta-item">
        <i class="fa-regular fa-pen-fancy"></i>&nbsp;
        <span class="desktop">2023-06-05 16:53:30</span>
        <span class="mobile">2023-06-05 16:53</span>
        <span class="hover-info">创建</span>
    </span>
    
        <span class="article-date article-meta-item">
            <i class="fa-regular fa-wrench"></i>&nbsp;
            <span class="desktop">2023-06-05 00:53:56</span>
            <span class="mobile">2023-06-05 00:53</span>
            <span class="hover-info">更新</span>
        </span>
    

    
        <span class="article-categories article-meta-item">
            <i class="fa-regular fa-folders"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/categories/Kotlin/">Kotlin</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    
    
        <span class="article-tags article-meta-item">
            <i class="fa-regular fa-tags"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/tags/%E5%8D%8F%E7%A8%8B/">协程</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    

    
    
    
    
        <span class="article-pv article-meta-item">
            <i class="fa-regular fa-eye"></i>&nbsp;<span id="busuanzi_value_page_pv"></span>
        </span>
    
</div>

                        </div>
                    </div>
                </div>
            

            <div class="article-content markdown-body">
                <h1 id="kotlin协程"><a class="markdownIt-Anchor" href="#kotlin协程">#</a> Kotlin 协程</h1>
<h2 id="一-kotlin-协程"><a class="markdownIt-Anchor" href="#一-kotlin-协程">#</a> 一、Kotlin 协程</h2>
<p>Kotlin <a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Flinks.jianshu.com%2Fgo%3Fto%3Dhttps%3A%2F%2FKotlinlang.org%2Fdocs%2Freference%2Fcoroutines-overview.html" >协程 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>提供了一种全新处理并发的方式，你可以在 Android 平台上使用它来简化异步执行的代码。协程从 Kotlin 1.3 版本开始引入，但这一概念在编程世界诞生的黎明之际就有了，最早使用协程的编程语言可以追溯到 1967 年的 <a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Flinks.jianshu.com%2Fgo%3Fto%3Dhttps%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSimula" >Simula <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a> 语言。在过去几年间，协程这个概念发展势头迅猛，现已经被诸多主流编程语言采用，比如 <a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Flinks.jianshu.com%2Fgo%3Fto%3Dhttps%3A%2F%2Fjavascript.info%2Fasync-await" >Javascript <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>、<a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Flinks.jianshu.com%2Fgo%3Fto%3Dhttps%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fdotnet%2Fcsharp%2Fprogramming-guide%2Fconcepts%2Fasync%2F" >C# <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>、<a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Flinks.jianshu.com%2Fgo%3Fto%3Dhttps%3A%2F%2Fdocs.python.org%2F3%2Flibrary%2Fasyncio-task.html" >Python <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>、<a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Flinks.jianshu.com%2Fgo%3Fto%3Dhttps%3A%2F%2Fruby-doc.org%2Fcore-2.1.1%2FFiber.html" >Ruby <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a> 以及 <a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Flinks.jianshu.com%2Fgo%3Fto%3Dhttps%3A%2F%2Ftour.golang.org%2Fconcurrency%2F1" >Go <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a> 等。Kotlin 协程是基于来自其他语言的既定概念</p>
<p>Google 官方推荐将 Kotlin 协程作为在 Android 上进行异步编程的解决方案，值得关注的功能点包括：</p>
<ul>
<li><strong>轻量</strong>：你可以在单个线程上运行多个协程，因为协程支持<a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2FKotlinlang.org%2Fdocs%2Freference%2Fcoroutines%2Fbasics.html" >挂起 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>，不会使正在运行协程的线程阻塞。挂起比阻塞节省内存，且支持多个并行操作</li>
<li><strong>内存泄露更少</strong>：使用<a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2FKotlinlang.org%2Fdocs%2Freference%2Fcoroutines%2Fbasics.html%23structured-concurrency" >结构化并发 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>机制在一个作用域内执行多个操作</li>
<li><strong>内置取消支持</strong>：<a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2FKotlinlang.org%2Fdocs%2Freference%2Fcoroutines%2Fcancellation-and-timeouts.html" >取消 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>功能会自动通过正在运行的协程层次结构传播</li>
<li><strong>Jetpack 集成</strong>：许多 Jetpack 库都包含提供全面协程支持的<a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.google.cn%2FKotlin%2Fktx" >扩展 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>。某些库还提供自己的<a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.google.cn%2Ftopic%2Flibraries%2Farchitecture%2Fcoroutines" >协程作用域 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>，可供你用于结构化并发</li>
</ul>
<p>引入依赖：</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">implementation <span class="string">&#x27;org.jetbrains.Kotlinx:Kotlinx-coroutines-core:1.4.2&#x27;</span></span><br><span class="line">implementation <span class="string">&#x27;org.jetbrains.Kotlinx:Kotlinx-coroutines-android:1.4.2&#x27;</span></span><br></pre></td></tr></table></figure></div>
<h2 id="二-第一个协程"><a class="markdownIt-Anchor" href="#二-第一个协程">#</a> 二、第一个协程</h2>
<p>协程可以称为<strong>轻量级线程</strong>。Kotlin 协程在 CoroutineScope 的上下文中通过 launch、async 等<strong>协程构造器</strong>（CoroutineBuilder）来声明并启动</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    GlobalScope.launch(context = Dispatchers.IO) &#123;</span><br><span class="line">        <span class="comment">//延时一秒</span></span><br><span class="line">        delay(<span class="number">1000</span>)</span><br><span class="line">        log(<span class="string">&quot;launch&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//主动休眠两秒，防止JVM过快退出</span></span><br><span class="line">    Thread.sleep(<span class="number">2000</span>)</span><br><span class="line">    log(<span class="string">&quot;end&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">log</span><span class="params">(msg: <span class="type">Any</span>?)</span></span> = println(<span class="string">&quot;[<span class="subst">$&#123;Thread.currentThread().name&#125;</span>] <span class="variable">$msg</span>&quot;</span>)</span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span> <span class="meta">@coroutine</span>#<span class="number">1</span>] launch</span><br><span class="line">[main] end</span><br></pre></td></tr></table></figure></div>
<p>在上面的例子中，通过 GlobalScope（即<strong>全局作用域</strong>）启动了一个协程，在延迟一秒后输出一行日志。从输出结果可以看出来，启动的协程是运行在协程内部的<strong>线程池</strong>中。虽然从表现结果上来看，启动一个协程类似于我们直接使用 Thread 来执行耗时任务，但实际上协程和线程有着本质上的区别。通过使用协程，可以极大的提高线程的并发效率，避免以往的嵌套回调地狱，极大提高了代码的可读性</p>
<p>以上代码就涉及到了协程的四个基础概念：</p>
<ul>
<li>suspend function。即挂起函数，delay 函数就是协程库提供的一个用于实现非阻塞式延时的挂起函数</li>
<li>CoroutineScope。即协程作用域，GlobalScope 是 CoroutineScope 的一个实现类，用于指定协程的作用范围，可用于管理多个协程的生命周期，所有协程都需要通过 CoroutineScope 来启动</li>
<li>CoroutineContext。即协程上下文，包含多种类型的配置参数。<a class="link"   target="_blank" rel="noopener" href="http://Dispatchers.IO" >Dispatchers.IO <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a> 就是 CoroutineContext 这个抽象概念的一种实现，用于指定协程的运行载体，即用于指定协程要运行在哪类线程上</li>
<li>CoroutineBuilder。即协程构建器，协程在 CoroutineScope 的上下文中通过 launch、async 等协程构建器来进行声明并启动。launch、async 等均被声明 CoroutineScope 的扩展方法</li>
</ul>
<h2 id="三-suspend-function"><a class="markdownIt-Anchor" href="#三-suspend-function">#</a> 三、suspend function</h2>
<p>如果上述例子试图直接在 GlobalScope 外调用  <code>delay()</code>  函数的话，IDE 就会提示一个错误：<strong>Suspend function ‘delay’ should be called only from a coroutine or another suspend function</strong>。意思是： <code>delay()</code>  函数是一个挂起函数，只能由协程或者由其它挂起函数来调用</p>
<p><code>delay()</code>  函数就使用了 suspend 进行修饰，用 suspend 修饰的函数就是挂起函数</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">delay</span><span class="params">(timeMillis: <span class="type">Long</span>)</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>读者在网上看关于协程的文章的时候，应该经常会看到这么一句话：<strong>挂起函数不会阻塞其所在线程，而是会将协程挂起，在特定的时候才再恢复协程</strong></p>
<p>对于这句话我的理解是： <code>delay()</code>  函数类似于 Java 中的  <code>Thread.sleep()</code> ，而之所以说  <code>delay()</code>  函数是非阻塞的，是因为它和单纯的线程休眠有着本质的区别。协程是运行于线程上的，一个线程可以运行多个（几千上万个）协程。线程的调度行为是由操作系统来管理的，而协程的调度行为是可以由开发者来指定并由编译器来实现的，协程能够细粒度地控制多个任务的执行时机和执行线程，当某个特定的线程上的所有协程被 suspend 后，该线程便可腾出资源去处理其他任务</p>
<p>例如，当在 ThreadA 上运行的 CoroutineA 调用了 <code>delay(1000L)</code>  函数指定延迟一秒后再运行，ThreadA 会转而去执行 CoroutineB，等到一秒后再来继续执行 CoroutineA。所以，ThreadA 并不会因为 CoroutineA 的延时而阻塞，而是能继续去执行其它任务，所以挂起函数并不会阻塞其所在线程，这样就极大地提高了线程的并发灵活度，最大化了线程的利用效率。而如果是使用 <code>Thread.sleep()</code>  的话，线程就只能干等着而不会去执行其它任务，降低了线程的利用效率</p>
<h2 id="四-suspend-function-的挂起与恢复"><a class="markdownIt-Anchor" href="#四-suspend-function-的挂起与恢复">#</a> 四、suspend function 的挂起与恢复</h2>
<p>协程在常规函数的基础上添加了两项操作用于处理长时间运行的任务。在 <code>invoke</code> （或  <code>call</code> ）和 <code>return</code>  之外，协程添加了 <code>suspend</code>  和  <code>resume</code> ：</p>
<ul>
<li><code>suspend</code>  用于暂停执行当前协程，并保存所有局部变量</li>
<li><code>resume</code>  用于让已暂停的协程从暂停处继续执行</li>
</ul>
<p>suspend 函数只能由其它 suspend 函数调用，或者是由协程来调用</p>
<p>以下示例展示了一项任务（假设 get 方法是一个网络请求任务）的简单协程实现：</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">fetchDocs</span><span class="params">()</span></span> &#123;                             <span class="comment">// Dispatchers.Main</span></span><br><span class="line">    <span class="keyword">val</span> result = <span class="keyword">get</span>(<span class="string">&quot;https://developer.android.com&quot;</span>) <span class="comment">// Dispatchers.IO for `get`</span></span><br><span class="line">    show(result)                                      <span class="comment">// Dispatchers.Main</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">get</span><span class="params">(url: <span class="type">String</span>)</span></span> = withContext(Dispatchers.IO) &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>在上面的示例中， <code>get()</code>  仍在主线程上被调用，但它会在启动网络请求之前暂停协程。 <code>get()</code>  主体内通过调用  <code>withContext(Dispatchers.IO)</code>  创建了一个在 IO 线程池中运行的代码块，在该块内的任何代码都始终通过 IO 调度器执行。当网络请求完成后， <code>get()</code>  会恢复已暂停的协程，使得主线程协程可以直接拿到网络请求结果而不用使用回调来通知主线程。Retrofit 就是以这种方式来实现对协程的支持的</p>
<p>Kotlin 使用堆栈帧管理要运行哪个函数以及所有局部变量。暂停协程时，系统会复制并保存当前的堆栈帧以供稍后使用。恢复时，会将堆栈帧从其保存位置复制回来，然后函数再次开始运行。即使代码可能看起来像普通的顺序阻塞请求，协程也能确保网络请求避免阻塞主线程</p>
<p>在主线程进行的<strong>暂停协程</strong>和<strong>恢复协程</strong>的两个操作，既实现了将耗时任务交由后台线程完成，保障了主线程安全，又以同步代码的方式完成了实际上的多线程异步调用。可以说，在 Android 平台上协程主要就用来解决两个问题：</p>
<ol>
<li><strong>处理耗时任务 (Long running tasks)</strong>，这种任务常常会阻塞住主线程</li>
<li><strong>保证主线程安全 (Main-safety)</strong> ，即确保安全地从主线程调用任何 suspend 函数</li>
</ol>
<h2 id="五-coroutinescope"><a class="markdownIt-Anchor" href="#五-coroutinescope">#</a> 五、CoroutineScope</h2>
<p>CoroutineScope 即<strong>协程作用域</strong>，用于对协程进行追踪。如果我们启动了多个协程但是没有一个可以对其进行统一管理的途径的话，那么就会导致我们的代码臃肿杂乱，甚至发生<strong>内存泄露</strong>或者<strong>任务泄露</strong>。为了确保所有的协程都会被追踪，Kotlin 不允许在没有使用 CoroutineScope 的情况下启动新的协程。CoroutineScope 可被看作是一个具有超能力的 ExecutorService 的轻量级版本。它能启动新的协程，同时这个协程还具备上文所说的 suspend 和 resume 的优势</p>
<p>所有的协程都需要通过 CoroutineScope 来启动，它会跟踪它使用  <code>launch</code>  或  <code>async</code>  创建的所有协程，你可以随时调用  <code>scope.cancel()</code>  取消正在运行的协程。CoroutineScope 本身并不运行协程，它只是确保你不会失去对协程的追踪，即使协程被挂起也是如此。在 Android 中，某些 KTX 库为某些生命周期类提供了自己的  <code>CoroutineScope</code> 。例如， <code>ViewModel</code>  有 <a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.google.cn%2Freference%2FKotlin%2Fandroidx%2Flifecycle%2Fpackage-summary%23(androidx.lifecycle.ViewModel).viewModelScope%3AKotlinx.coroutines.CoroutineScope"> <code>viewModelScope</code> </a>， <code>Lifecycle</code>  有 <a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.google.cn%2Freference%2FKotlin%2Fandroidx%2Flifecycle%2Fpackage-summary%23lifecyclescope"> <code>lifecycleScope</code> </a></p>
<p>CoroutineScope 大体上可以分为三种：</p>
<ul>
<li>GlobalScope。即全局协程作用域，在这个范围内启动的协程可以一直运行直到应用停止运行。GlobalScope 本身不会阻塞当前线程，且启动的协程相当于守护线程，不会阻止 JVM 结束运行</li>
<li>runBlocking。一个顶层函数，和 GlobalScope 不一样，它会阻塞当前线程直到其内部所有相同作用域的协程执行结束</li>
<li>自定义 CoroutineScope。可用于实现主动控制协程的生命周期范围，对于 Android 开发来说最大意义之一就是可以避免内存泄露</li>
</ul>
<h4 id="1-globalscope"><a class="markdownIt-Anchor" href="#1-globalscope">#</a> 1、GlobalScope</h4>
<p>GlobalScope 属于<strong>全局作用域</strong>，这意味着通过 GlobalScope 启动的协程的生命周期只受整个应用程序的生命周期的限制，只要整个应用程序还在运行且协程的任务还未结束，协程就可以一直运行</p>
<p>GlobalScope 不会阻塞其所在线程，所以以下代码中主线程的日志会早于 GlobalScope 内部输出日志。此外，GlobalScope 启动的协程相当于守护线程，不会阻止 JVM 结束运行，所以如果将主线程的休眠时间改为三百毫秒的话，就不会看到 launch A 输出日志</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    log(<span class="string">&quot;start&quot;</span>)</span><br><span class="line">    GlobalScope.launch &#123;</span><br><span class="line">        launch &#123;</span><br><span class="line">            delay(<span class="number">400</span>)</span><br><span class="line">            log(<span class="string">&quot;launch A&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        launch &#123;</span><br><span class="line">            delay(<span class="number">300</span>)</span><br><span class="line">            log(<span class="string">&quot;launch B&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        log(<span class="string">&quot;GlobalScope&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    log(<span class="string">&quot;end&quot;</span>)</span><br><span class="line">    Thread.sleep(<span class="number">500</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] start</span><br><span class="line">[main] end</span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span> <span class="meta">@coroutine</span>#<span class="number">1</span>] GlobalScope</span><br><span class="line">[DefaultDispatcher-worker-<span class="number">3</span> <span class="meta">@coroutine</span>#<span class="number">3</span>] launch B</span><br><span class="line">[DefaultDispatcher-worker-<span class="number">3</span> <span class="meta">@coroutine</span>#<span class="number">2</span>] launch A</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p><code>GlobalScope.launch</code>  会创建一个顶级协程，尽管它很轻量级，但在运行时还是会消耗一些内存资源，且可以一直运行直到整个应用程序停止（只要任务还未结束），这可能会导致内存泄露，所以在日常开发中应该谨慎使用 GlobalScope</p>
<h4 id="2-runblocking"><a class="markdownIt-Anchor" href="#2-runblocking">#</a> 2、runBlocking</h4>
<p>也可以使用 runBlocking 这个顶层函数来启动协程，runBlocking 函数的第二个参数即协程的执行体，该参数被声明为 CoroutineScope 的扩展函数，因此执行体就包含了一个隐式的 CoroutineScope，所以在 runBlocking 内部可以来直接启动协程</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="type">&lt;T&gt;</span> <span class="title">runBlocking</span><span class="params">(context: <span class="type">CoroutineContext</span> = EmptyCoroutineContext, block: <span class="type">suspend</span> <span class="type">CoroutineScope</span>.() -&gt; <span class="type">T</span>)</span></span>: T</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>runBlocking 的一个方便之处就是：只有当内部<strong>相同作用域</strong>的所有协程都运行结束后，声明在 runBlocking 之后的代码才能执行，即 runBlocking 会阻塞其所在线程</p>
<p>看以下代码。runBlocking 内部启动的两个协程会各自做耗时操作，从输出结果可以看出来两个协程还是在交叉并发执行，且 runBlocking 会等到两个协程都执行结束后才会退出，外部的日志输出结果有明确的先后顺序。即 runBlocking 内部启动的协程是非阻塞式的，但 runBlocking 阻塞了其所在线程。此外，runBlocking 只会等待相同作用域的协程完成才会退出，而不会等待 GlobalScope 等其它作用域启动的协程</p>
<p><strong>所以说，runBlocking 本身带有阻塞线程的意味，但其内部运行的协程又是非阻塞的，读者需要意会这两者的区别</strong></p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    log(<span class="string">&quot;start&quot;</span>)</span><br><span class="line">    runBlocking &#123;</span><br><span class="line">        launch &#123;</span><br><span class="line">            repeat(<span class="number">3</span>) &#123;</span><br><span class="line">                delay(<span class="number">100</span>)</span><br><span class="line">                log(<span class="string">&quot;launchA - <span class="variable">$it</span>&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        launch &#123;</span><br><span class="line">            repeat(<span class="number">3</span>) &#123;</span><br><span class="line">                delay(<span class="number">100</span>)</span><br><span class="line">                log(<span class="string">&quot;launchB - <span class="variable">$it</span>&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        GlobalScope.launch &#123;</span><br><span class="line">            repeat(<span class="number">3</span>) &#123;</span><br><span class="line">                delay(<span class="number">120</span>)</span><br><span class="line">                log(<span class="string">&quot;GlobalScope - <span class="variable">$it</span>&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    log(<span class="string">&quot;end&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] start</span><br><span class="line">[main] launchA - <span class="number">0</span></span><br><span class="line">[main] launchB - <span class="number">0</span></span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span>] GlobalScope - <span class="number">0</span></span><br><span class="line">[main] launchA - <span class="number">1</span></span><br><span class="line">[main] launchB - <span class="number">1</span></span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span>] GlobalScope - <span class="number">1</span></span><br><span class="line">[main] launchA - <span class="number">2</span></span><br><span class="line">[main] launchB - <span class="number">2</span></span><br><span class="line">[main] end</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>基于是否会阻塞线程的区别，以下代码中 runBlocking 会早于 GlobalScope 输出日志</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    GlobalScope.launch(Dispatchers.IO) &#123;</span><br><span class="line">        delay(<span class="number">600</span>)</span><br><span class="line">        log(<span class="string">&quot;GlobalScope&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    runBlocking &#123;</span><br><span class="line">        delay(<span class="number">500</span>)</span><br><span class="line">        log(<span class="string">&quot;runBlocking&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//主动休眠两百毫秒，使得和 runBlocking 加起来的延迟时间少于六百毫秒</span></span><br><span class="line">    Thread.sleep(<span class="number">200</span>)</span><br><span class="line">    log(<span class="string">&quot;after sleep&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] runBlocking</span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span>] GlobalScope</span><br><span class="line">[main] after sleep</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="3-coroutinescope"><a class="markdownIt-Anchor" href="#3-coroutinescope">#</a> 3、coroutineScope</h4>
<p><code>coroutineScope</code>  函数用于创建一个独立的协程作用域，直到所有启动的协程都完成后才结束自身。 <code>runBlocking</code>  和  <code>coroutineScope</code>  看起来很像，因为它们都需要等待其内部所有相同作用域的协程结束后才会结束自己。两者的主要区别在于  <code>runBlocking</code>  方法会阻塞当前线程，而  <code>coroutineScope</code>  不会阻塞线程，而是会挂起并释放底层线程以供其它协程使用。由于这个差别， <code>runBlocking</code>  是一个普通函数，而  <code>coroutineScope</code>  是一个挂起函数</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    launch &#123;</span><br><span class="line">        delay(<span class="number">100</span>)</span><br><span class="line">        log(<span class="string">&quot;Task from runBlocking&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    coroutineScope &#123;</span><br><span class="line">        launch &#123;</span><br><span class="line">            delay(<span class="number">500</span>)</span><br><span class="line">            log(<span class="string">&quot;Task from nested launch&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        delay(<span class="number">100</span>)</span><br><span class="line">        log(<span class="string">&quot;Task from coroutine scope&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    log(<span class="string">&quot;Coroutine scope is over&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] Task from coroutine scope</span><br><span class="line">[main] Task from runBlocking</span><br><span class="line">[main] Task from nested launch</span><br><span class="line">[main] Coroutine scope <span class="keyword">is</span> over</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="4-supervisorscope"><a class="markdownIt-Anchor" href="#4-supervisorscope">#</a> 4、supervisorScope</h4>
<p><code>supervisorScope</code>  函数用于创建一个使用了 SupervisorJob 的 coroutineScope，该作用域的特点就是抛出的异常不会连锁取消同级协程和父协程</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    launch &#123;</span><br><span class="line">        delay(<span class="number">100</span>)</span><br><span class="line">        log(<span class="string">&quot;Task from runBlocking&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    supervisorScope &#123;</span><br><span class="line">        launch &#123;</span><br><span class="line">            delay(<span class="number">500</span>)</span><br><span class="line">            log(<span class="string">&quot;Task throw Exception&quot;</span>)</span><br><span class="line">            <span class="keyword">throw</span> Exception(<span class="string">&quot;failed&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        launch &#123;</span><br><span class="line">            delay(<span class="number">600</span>)</span><br><span class="line">            log(<span class="string">&quot;Task from nested launch&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    log(<span class="string">&quot;Coroutine scope is over&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">2</span>] Task from runBlocking</span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">3</span>] Task <span class="keyword">throw</span> Exception</span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">4</span>] Task from nested launch</span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">1</span>] Coroutine scope <span class="keyword">is</span> over</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="5-自定义-coroutinescope"><a class="markdownIt-Anchor" href="#5-自定义-coroutinescope">#</a> 5、自定义 CoroutineScope</h4>
<p>假设我们在 Activity 中先后启动了多个协程用于执行异步耗时操作，那么当 Activity 退出时，必须取消所有协程以避免内存泄漏。我们可以通过保留每一个 Job 引用然后在  <code>onDestroy</code>  方法里来手动取消，但这种方式相当来说会比较繁琐和低效。kotlinx.coroutines 提供了 CoroutineScope 来管理多个协程的生命周期</p>
<p>我们可以通过创建与 Activity 生命周期相关联的协程作用域的实例来管理协程的生命周期。CoroutineScope 的实例可以通过  <code>CoroutineScope()</code>  或  <code>MainScope()</code>  的工厂函数来构建。前者创建通用作用域，后者创建 UI 应用程序的作用域并使用 Dispatchers.Main 作为默认的调度器</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Activity</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> mainScope = MainScope()</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">()</span></span> &#123;</span><br><span class="line">        mainScope.launch &#123;</span><br><span class="line">            repeat(<span class="number">5</span>) &#123;</span><br><span class="line">                delay(<span class="number">1000L</span> * it)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">onDestroy</span><span class="params">()</span></span> &#123;</span><br><span class="line">        mainScope.cancel()</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>或者，我们可以通过委托模式来让 Activity 实现 CoroutineScope 接口，从而可以在 Activity 内直接启动协程而不必显示地指定它们的上下文，并且在  <code>onDestroy()</code>  中自动取消所有协程</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Activity</span> : <span class="type">CoroutineScope</span> <span class="title">by</span> <span class="title">CoroutineScope</span>(Dispatchers.Default) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">()</span></span> &#123;</span><br><span class="line">        launch &#123;</span><br><span class="line">            repeat(<span class="number">5</span>) &#123;</span><br><span class="line">                delay(<span class="number">200L</span> * it)</span><br><span class="line">                log(it)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        log(<span class="string">&quot;Activity Created&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">onDestroy</span><span class="params">()</span></span> &#123;</span><br><span class="line">        cancel()</span><br><span class="line">        log(<span class="string">&quot;Activity Destroyed&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>从输出结果可以看出，当回调了 <code>onDestroy()</code>  方法后协程就不会再输出日志了</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> activity = Activity()</span><br><span class="line">    activity.onCreate()</span><br><span class="line">    delay(<span class="number">1000</span>)</span><br><span class="line">    activity.onDestroy()</span><br><span class="line">    delay(<span class="number">1000</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">1</span>] Activity Created</span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span> <span class="meta">@coroutine</span>#<span class="number">2</span>] <span class="number">0</span></span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span> <span class="meta">@coroutine</span>#<span class="number">2</span>] <span class="number">1</span></span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span> <span class="meta">@coroutine</span>#<span class="number">2</span>] <span class="number">2</span></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">1</span>] Activity Destroyed</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>已取消的作用域无法再创建协程。因此，仅当控制其生命周期的类被销毁时，才应调用  <code>scope.cancel()</code> 。例如，使用  <code>viewModelScope</code>  时，<a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.google.cn%2Ftopic%2Flibraries%2Farchitecture%2Fviewmodel"> <code>ViewModel</code> </a> 类会在 ViewModel 的  <code>onCleared()</code>  方法中自动取消作用域</p>
<h2 id="六-coroutinebuilder"><a class="markdownIt-Anchor" href="#六-coroutinebuilder">#</a> 六、CoroutineBuilder</h2>
<h4 id="1-launch"><a class="markdownIt-Anchor" href="#1-launch">#</a> 1、launch</h4>
<p>看下  <code>launch</code>  函数的方法签名。 <code>launch</code>  是一个作用于 CoroutineScope 的扩展函数，用于在不阻塞当前线程的情况下启动一个协程，并返回对该协程任务的引用，即 Job 对象</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> CoroutineScope.<span class="title">launch</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    context: <span class="type">CoroutineContext</span> = EmptyCoroutineContext,</span></span></span><br><span class="line"><span class="params"><span class="function">    start: <span class="type">CoroutineStart</span> = CoroutineStart.DEFAULT,</span></span></span><br><span class="line"><span class="params"><span class="function">    block: <span class="type">suspend</span> <span class="type">CoroutineScope</span>.() -&gt; <span class="type">Unit</span></span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>: Job</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p><code>launch</code>  函数共包含三个参数：</p>
<ol>
<li>context。用于指定协程的上下文</li>
<li>start。用于指定协程的启动方式。默认值为  <code>CoroutineStart.DEFAULT</code> ，即协程会在声明的同时就立即进入等待调度的状态，即可以立即执行的状态。可以通过将其设置为 <code>CoroutineStart.LAZY</code>  来实现延迟启动，即懒加载</li>
<li>block。用于传递协程的执行体，即希望交由协程执行的任务</li>
</ol>
<p>可以看到 launchA 和 launchB 是并行交叉执行的</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> launchA = launch &#123;</span><br><span class="line">        repeat(<span class="number">3</span>) &#123;</span><br><span class="line">            delay(<span class="number">100</span>)</span><br><span class="line">            log(<span class="string">&quot;launchA - <span class="variable">$it</span>&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">val</span> launchB = launch &#123;</span><br><span class="line">        repeat(<span class="number">3</span>) &#123;</span><br><span class="line">            delay(<span class="number">100</span>)</span><br><span class="line">            log(<span class="string">&quot;launchB - <span class="variable">$it</span>&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] launchA - <span class="number">0</span></span><br><span class="line">[main] launchB - <span class="number">0</span></span><br><span class="line">[main] launchA - <span class="number">1</span></span><br><span class="line">[main] launchB - <span class="number">1</span></span><br><span class="line">[main] launchA - <span class="number">2</span></span><br><span class="line">[main] launchB - <span class="number">2</span></span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="2-job"><a class="markdownIt-Anchor" href="#2-job">#</a> 2、Job</h4>
<p>Job 是协程的句柄。使用  <code>launch</code>  或  <code>async</code>  创建的每个协程都会返回一个  <code>Job</code>  实例，该实例唯一标识协程并管理其生命周期。Job 是一个接口类型，这里列举 Job 几个比较有用的属性和函数</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//当 Job 处于活动状态时为 true</span></span><br><span class="line"><span class="comment">//如果 Job 未被取消或没有失败，则均处于 active 状态</span></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">val</span> isActive: <span class="built_in">Boolean</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//当 Job 正常结束或者由于异常结束，均返回 true</span></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">val</span> isCompleted: <span class="built_in">Boolean</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//当 Job 被主动取消或者由于异常结束，均返回 true</span></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">val</span> isCancelled: <span class="built_in">Boolean</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//启动 Job</span></span><br><span class="line"><span class="comment">//如果此调用的确启动了 Job，则返回 true</span></span><br><span class="line"><span class="comment">//如果 Job 调用前就已处于 started 或者是 completed 状态，则返回 false </span></span><br><span class="line">   <span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="title">start</span><span class="params">()</span></span>: <span class="built_in">Boolean</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//用于取消 Job，可同时通过传入 Exception 来标明取消原因</span></span><br><span class="line">   <span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="title">cancel</span><span class="params">(cause: <span class="type">CancellationException</span>? = <span class="literal">null</span>)</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">//阻塞等待直到此 Job 结束运行</span></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">join</span><span class="params">()</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">//当 Job 结束运行时（不管由于什么原因）回调此方法，可用于接收可能存在的运行异常</span></span><br><span class="line">   <span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="title">invokeOnCompletion</span><span class="params">(handler: <span class="type">CompletionHandler</span>)</span></span>: DisposableHandle</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>Job 具有以下几种状态值，每种状态对应的属性值各不相同</p>
<table>
<thead>
<tr>
<th><strong>State</strong></th>
<th><a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2FKotlin.github.io%2FKotlinx.coroutines%2FKotlinx-coroutines-core%2FKotlinx.coroutines%2F-job%2Fis-active.html" >isActive <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></th>
<th><a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2FKotlin.github.io%2FKotlinx.coroutines%2FKotlinx-coroutines-core%2FKotlinx.coroutines%2F-job%2Fis-completed.html" >isCompleted <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></th>
<th><a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2FKotlin.github.io%2FKotlinx.coroutines%2FKotlinx-coroutines-core%2FKotlinx.coroutines%2F-job%2Fis-cancelled.html" >isCancelled <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></th>
</tr>
</thead>
<tbody>
<tr>
<td><em>New</em> (optional initial state)</td>
<td>false</td>
<td>false</td>
<td>false</td>
</tr>
<tr>
<td><em>Active</em> (default initial state)</td>
<td>true</td>
<td>false</td>
<td>false</td>
</tr>
<tr>
<td><em>Completing</em> (transient state)</td>
<td>true</td>
<td>false</td>
<td>false</td>
</tr>
<tr>
<td><em>Cancelling</em> (transient state)</td>
<td>false</td>
<td>false</td>
<td>true</td>
</tr>
<tr>
<td><em>Cancelled</em> (final state)</td>
<td>false</td>
<td>true</td>
<td>true</td>
</tr>
<tr>
<td><em>Completed</em> (final state)</td>
<td>false</td>
<td>true</td>
<td>false</td>
</tr>
</tbody>
</table>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">//将协程设置为延迟启动</span></span><br><span class="line">    <span class="keyword">val</span> job = GlobalScope.launch(start = CoroutineStart.LAZY) &#123;</span><br><span class="line">        <span class="keyword">for</span> (i <span class="keyword">in</span> <span class="number">0.</span><span class="number">.100</span>) &#123;</span><br><span class="line">            <span class="comment">//每循环一次均延迟一百毫秒</span></span><br><span class="line">            delay(<span class="number">100</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    job.invokeOnCompletion &#123;</span><br><span class="line">        log(<span class="string">&quot;invokeOnCompletion：<span class="variable">$it</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    log(<span class="string">&quot;1. job.isActive：<span class="subst">$&#123;job.isActive&#125;</span>&quot;</span>)</span><br><span class="line">    log(<span class="string">&quot;1. job.isCancelled：<span class="subst">$&#123;job.isCancelled&#125;</span>&quot;</span>)</span><br><span class="line">    log(<span class="string">&quot;1. job.isCompleted：<span class="subst">$&#123;job.isCompleted&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    job.start()</span><br><span class="line"></span><br><span class="line">    log(<span class="string">&quot;2. job.isActive：<span class="subst">$&#123;job.isActive&#125;</span>&quot;</span>)</span><br><span class="line">    log(<span class="string">&quot;2. job.isCancelled：<span class="subst">$&#123;job.isCancelled&#125;</span>&quot;</span>)</span><br><span class="line">    log(<span class="string">&quot;2. job.isCompleted：<span class="subst">$&#123;job.isCompleted&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment">//休眠四百毫秒后再主动取消协程</span></span><br><span class="line">    Thread.sleep(<span class="number">400</span>)</span><br><span class="line">    job.cancel(CancellationException(<span class="string">&quot;test&quot;</span>))</span><br><span class="line"></span><br><span class="line">    <span class="comment">//休眠四百毫秒防止JVM过快停止导致 invokeOnCompletion 来不及回调</span></span><br><span class="line">    Thread.sleep(<span class="number">400</span>)</span><br><span class="line"></span><br><span class="line">    log(<span class="string">&quot;3. job.isActive：<span class="subst">$&#123;job.isActive&#125;</span>&quot;</span>)</span><br><span class="line">    log(<span class="string">&quot;3. job.isCancelled：<span class="subst">$&#123;job.isCancelled&#125;</span>&quot;</span>)</span><br><span class="line">    log(<span class="string">&quot;3. job.isCompleted：<span class="subst">$&#123;job.isCompleted&#125;</span>&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] <span class="number">1.</span> job.isActive：<span class="literal">false</span></span><br><span class="line">[main] <span class="number">1.</span> job.isCancelled：<span class="literal">false</span></span><br><span class="line">[main] <span class="number">1.</span> job.isCompleted：<span class="literal">false</span></span><br><span class="line">[main] <span class="number">2.</span> job.isActive：<span class="literal">true</span></span><br><span class="line">[main] <span class="number">2.</span> job.isCancelled：<span class="literal">false</span></span><br><span class="line">[main] <span class="number">2.</span> job.isCompleted：<span class="literal">false</span></span><br><span class="line">[DefaultDispatcher-worker-<span class="number">2</span>] invokeOnCompletion：java.util.concurrent.CancellationException: test</span><br><span class="line">[main] <span class="number">3.</span> job.isActive：<span class="literal">false</span></span><br><span class="line">[main] <span class="number">3.</span> job.isCancelled：<span class="literal">true</span></span><br><span class="line">[main] <span class="number">3.</span> job.isCompleted：<span class="literal">true</span></span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="3-async"><a class="markdownIt-Anchor" href="#3-async">#</a> 3、async</h4>
<p>看下  <code>async</code>  函数的方法签名。 <code>async</code>  也是一个作用于 CoroutineScope 的扩展函数，和  <code>launch</code>  的区别主要就在于： <code>async</code>  可以返回协程的执行结果，而  <code>launch</code>  不行</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="type">&lt;T&gt;</span> CoroutineScope.<span class="title">async</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    context: <span class="type">CoroutineContext</span> = EmptyCoroutineContext,</span></span></span><br><span class="line"><span class="params"><span class="function">    start: <span class="type">CoroutineStart</span> = CoroutineStart.DEFAULT,</span></span></span><br><span class="line"><span class="params"><span class="function">    block: <span class="type">suspend</span> <span class="type">CoroutineScope</span>.() -&gt; <span class="type">T</span></span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>: Deferred&lt;T&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>通过 <code>await()</code>  方法可以拿到 async 协程的执行结果，可以看到两个协程的总耗时是远少于七秒的，总耗时基本等于耗时最长的协程</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">val</span> time = measureTimeMillis &#123;</span><br><span class="line">        runBlocking &#123;</span><br><span class="line">            <span class="keyword">val</span> asyncA = async &#123;</span><br><span class="line">                delay(<span class="number">3000</span>)</span><br><span class="line">                <span class="number">1</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">val</span> asyncB = async &#123;</span><br><span class="line">                delay(<span class="number">4000</span>)</span><br><span class="line">                <span class="number">2</span></span><br><span class="line">            &#125;</span><br><span class="line">            log(asyncA.await() + asyncB.await())</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    log(time)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] <span class="number">3</span></span><br><span class="line">[main] <span class="number">4070</span></span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<blockquote>
<p>由于 launch 和 async 仅能够在 CouroutineScope 中使用，所以任何创建的协程都会被该 scope 追踪。Kotlin 禁止创建不能够被追踪的协程，从而避免协程泄漏</p>
</blockquote>
<h4 id="4-async-的错误用法"><a class="markdownIt-Anchor" href="#4-async-的错误用法">#</a> 4、async 的错误用法</h4>
<p>修改下上述代码，可以发现两个协程的总耗时就会变为七秒左右</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">val</span> time = measureTimeMillis &#123;</span><br><span class="line">        runBlocking &#123;</span><br><span class="line">            <span class="keyword">val</span> asyncA = async(start = CoroutineStart.LAZY) &#123;</span><br><span class="line">                delay(<span class="number">3000</span>)</span><br><span class="line">                <span class="number">1</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">val</span> asyncB = async(start = CoroutineStart.LAZY) &#123;</span><br><span class="line">                delay(<span class="number">4000</span>)</span><br><span class="line">                <span class="number">2</span></span><br><span class="line">            &#125;</span><br><span class="line">            log(asyncA.await() + asyncB.await())</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    log(time)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] <span class="number">3</span></span><br><span class="line">[main] <span class="number">7077</span></span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>会造成这不同区别是因为  <code>CoroutineStart.LAZY</code>  不会主动启动协程，而是直到调用 <code>async.await()</code>  或者 <code>async.satrt()</code>  后才会启动（即懒加载模式），所以 <code>asyncA.await() + asyncB.await()</code>  会导致两个协程其实是在顺序执行。而默认值  <code>CoroutineStart.DEFAULT</code>  参数会使得协程在声明的同时就被启动了（实际上还需要等待被调度执行，但可以看做是立即就执行了），所以即使  <code>async.await()</code>  会阻塞当前线程直到协程返回结果值，但两个协程其实都是处于运行状态，所以总耗时就是四秒左右</p>
<p>此时可以通过先调用 <code>start()</code>  再调用 <code>await()</code>  来实现第一个例子的效果</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">asyncA.start()</span><br><span class="line">asyncB.start()</span><br><span class="line">log(asyncA.await() + asyncB.await())</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="5-async-并行分解"><a class="markdownIt-Anchor" href="#5-async-并行分解">#</a> 5、async 并行分解</h4>
<p>由  <code>suspend</code>  函数启动的所有协程都必须在该函数返回结果时停止，因此你可能需要保证这些协程在返回结果之前完成。借助 Kotlin 中的结构化并发机制，你可以定义用于启动一个或多个协程的  <code>coroutineScope</code> 。然后，你可以使用  <code>await()</code> （针对单个协程）或  <code>awaitAll()</code> （针对多个协程）保证这些协程在从函数返回结果之前完成</p>
<p>例如，假设我们定义一个用于异步获取两个文档的  <code>coroutineScope</code> 。通过对每个延迟引用调用  <code>await()</code> ，我们可以保证这两项  <code>async</code>  操作在返回值之前完成：</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">fetchTwoDocs</span><span class="params">()</span></span> =</span><br><span class="line">   	coroutineScope &#123;</span><br><span class="line">       	<span class="keyword">val</span> deferredOne = async &#123; fetchDoc(<span class="number">1</span>) &#125;</span><br><span class="line">       	<span class="keyword">val</span> deferredTwo = async &#123; fetchDoc(<span class="number">2</span>) &#125;</span><br><span class="line">       	deferredOne.await()</span><br><span class="line">       	deferredTwo.await()</span><br><span class="line">  	&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>你还可以对集合使用  <code>awaitAll()</code> ，如以下示例所示：</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">fetchTwoDocs</span><span class="params">()</span></span> =        <span class="comment">// called on any Dispatcher (any thread, possibly Main)</span></span><br><span class="line">    coroutineScope &#123;</span><br><span class="line">        <span class="keyword">val</span> deferreds = listOf(     <span class="comment">// fetch two docs at the same time</span></span><br><span class="line">            async &#123; fetchDoc(<span class="number">1</span>) &#125;,  <span class="comment">// async returns a result for the first doc</span></span><br><span class="line">            async &#123; fetchDoc(<span class="number">2</span>) &#125;   <span class="comment">// async returns a result for the second doc</span></span><br><span class="line">        )</span><br><span class="line">        deferreds.awaitAll()        <span class="comment">// use awaitAll to wait for both network requests</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>虽然  <code>fetchTwoDocs()</code>  使用  <code>async</code>  启动新协程，但该函数使用  <code>awaitAll()</code>  等待启动的协程完成后才会返回结果。不过请注意，即使我们没有调用  <code>awaitAll()</code> ， <code>coroutineScope</code>  构建器也会等到所有新协程都完成后才恢复名为  <code>fetchTwoDocs</code>  的协程。此外， <code>coroutineScope</code>  会捕获协程抛出的所有异常，并将其传送回调用方</p>
<h4 id="6-deferred"><a class="markdownIt-Anchor" href="#6-deferred">#</a> 6、Deferred</h4>
<p><code>async</code>  函数的返回值是一个 Deferred 对象。Deferred 是一个接口类型，继承于 Job 接口，所以 Job 包含的属性和方法 Deferred 都有，其主要就是在 Job 的基础上扩展了  <code>await()</code>  方法</p>
<h2 id="七-coroutinecontext"><a class="markdownIt-Anchor" href="#七-coroutinecontext">#</a> 七、CoroutineContext</h2>
<p>CoroutineContext 使用以下元素集定义协程的行为：</p>
<ul>
<li>Job：控制协程的生命周期</li>
<li>CoroutineDispatcher：将工作分派到适当的线程</li>
<li>CoroutineName：协程的名称，可用于调试</li>
<li>CoroutineExceptionHandler：处理未捕获的异常</li>
</ul>
<h4 id="1-job"><a class="markdownIt-Anchor" href="#1-job">#</a> 1、Job</h4>
<p>协程中的 Job 是其上下文 CoroutineContext 中的一部分，可以通过  <code>coroutineContext[Job]</code>  表达式从上下文中获取到</p>
<p>以下两个 log 语句虽然是运行在不同的协程上，但是其指向的 Job 其实是同个对象</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> job = launch &#123;</span><br><span class="line">        log(<span class="string">&quot;My job is <span class="subst">$&#123;coroutineContext[Job]&#125;</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    log(<span class="string">&quot;My job is <span class="variable">$job</span>&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">1</span>] My job <span class="keyword">is</span> <span class="string">&quot;coroutine#2&quot;</span>:StandaloneCoroutine&#123;Active&#125;@75a1cd57</span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">2</span>] My job <span class="keyword">is</span> <span class="string">&quot;coroutine#2&quot;</span>:StandaloneCoroutine&#123;Active&#125;@75a1cd57</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>实际上 CoroutineScope 的  <code>isActive</code>  这个扩展属性只是  <code>coroutineContext[Job]?.isActive == true</code>  的一种简便写法</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">val</span> CoroutineScope.isActive: <span class="built_in">Boolean</span></span><br><span class="line">    <span class="keyword">get</span>() = coroutineContext[Job]?.isActive ?: <span class="literal">true</span></span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="2-coroutinedispatcher"><a class="markdownIt-Anchor" href="#2-coroutinedispatcher">#</a> 2、CoroutineDispatcher</h4>
<p>CoroutineContext 包含一个 CoroutineDispatcher（协程调度器）用于指定执行协程的目标载体，即<strong>运行于哪个线程</strong>。CoroutineDispatcher 可以将协程的执行操作限制在特定线程上，也可以将其分派到线程池中，或者让它无限制地运行。所有的协程构造器（如 launch 和 async）都接受一个可选参数，即 CoroutineContext ，该参数可用于显式指定要创建的协程和其它上下文元素所要使用的 CoroutineDispatcher</p>
<p>要在主线程之外运行代码，可以让 Kotlin 协程在 Default 或 IO 调度程序上执行工作。在 Kotlin 中，所有协程都必须在 CoroutineDispatcher 中运行，即使它们在主线程上运行也是如此。协程可以自行暂停，而 CoroutineDispatcher 负责将其恢复</p>
<p>Kotlin 协程库提供了四个 Dispatcher 用于指定在何处运行协程，大部分情况下我们只会接触以下三个：</p>
<ul>
<li><strong>Dispatchers.Main</strong> - 使用此调度程序可在 Android 主线程上运行协程。此调度程序只能用于与界面交互和执行快速工作。示例包括调用  <code>suspend</code>  函数、运行 Android 界面框架操作，以及更新 <a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.google.cn%2Ftopic%2Flibraries%2Farchitecture%2Flivedata"> <code>LiveData</code> </a> 对象</li>
<li><strong><a class="link"   href="https://link.juejin.cn?target=http%3A%2F%2FDispatchers.IO" >Dispatchers.IO <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a></strong> - 此调度程序经过了专门优化，适合在主线程之外执行磁盘或网络 I/O。示例包括使用 <a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.google.cn%2Ftopic%2Flibraries%2Farchitecture%2Froom" >Room 组件 <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a>、从文件中读取数据或向文件中写入数据，以及运行任何网络操作</li>
<li><strong>Dispatchers.Default</strong> - 此调度程序经过了专门优化，适合在主线程之外执行占用大量 CPU 资源的工作。用例示例包括对列表排序和解析 JSON</li>
</ul>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking&lt;<span class="built_in">Unit</span>&gt; &#123;</span><br><span class="line">    launch &#123;</span><br><span class="line">        log(<span class="string">&quot;main runBlocking&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    launch(Dispatchers.Default) &#123;</span><br><span class="line">        log(<span class="string">&quot;Default&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    launch(Dispatchers.IO) &#123;</span><br><span class="line">        log(<span class="string">&quot;IO&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    launch(newSingleThreadContext(<span class="string">&quot;MyOwnThread&quot;</span>)) &#123;</span><br><span class="line">        log(<span class="string">&quot;newSingleThreadContext&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span> <span class="meta">@coroutine</span>#<span class="number">3</span>] Default</span><br><span class="line">[DefaultDispatcher-worker-<span class="number">2</span> <span class="meta">@coroutine</span>#<span class="number">4</span>] IO</span><br><span class="line">[MyOwnThread <span class="meta">@coroutine</span>#<span class="number">5</span>] newSingleThreadContext</span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">2</span>] main runBlocking</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>当  <code>launch {...}</code>  在不带参数的情况下使用时，它从外部的协程作用域继承上下文和调度器，即和 runBlocking 保持一致。而在 GlobalScope 中启动协程时默认使用的调度器是 Dispatchers.default，并使用共享的后台线程池，因此  <code>launch(Dispatchers.default){...}</code>  与  <code>GlobalScope.launch{...}</code>  是使用相同的调度器。 <code>newSingleThreadContext</code>  用于为协程专门创建一个新的线程来运行，专用线程是一种成本非常昂贵的资源，在实际的应用程序中必须在不再需要时释放掉，或者存储在顶级变量中以便在整个应用程序中进行重用</p>
<h4 id="3-withcontext"><a class="markdownIt-Anchor" href="#3-withcontext">#</a> 3、withContext</h4>
<p>对于以下代码， <code>get</code>  方法内使用 <code>withContext(Dispatchers.IO)</code>  创建了一个指定在 IO 线程池中运行的代码块，该区间内的任何代码都始终通过 IO 线程来执行。由于  <code>withContext</code>  方法本身就是一个挂起函数，因此  <code>get</code>  方法也必须定义为挂起函数</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">fetchDocs</span><span class="params">()</span></span> &#123;                      <span class="comment">// Dispatchers.Main</span></span><br><span class="line">    <span class="keyword">val</span> result = <span class="keyword">get</span>(<span class="string">&quot;developer.android.com&quot;</span>)  <span class="comment">// Dispatchers.Main</span></span><br><span class="line">    show(result)                               <span class="comment">// Dispatchers.Main</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">get</span><span class="params">(url: <span class="type">String</span>)</span></span> =                 <span class="comment">// Dispatchers.Main</span></span><br><span class="line">    withContext(Dispatchers.IO) &#123;              <span class="comment">// Dispatchers.IO (main-safety block)</span></span><br><span class="line">        <span class="comment">/* perform network IO here */</span>          <span class="comment">// Dispatchers.IO (main-safety block)</span></span><br><span class="line">    &#125;                                          <span class="comment">// Dispatchers.Main</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>借助协程，你可以细粒度地来调度线程。由于 <code>withContext()</code>  支持让你在不引入回调的情况下控制任何代码的执行线程池，因此你可以将其应用于非常小的函数，例如从数据库中读取数据或执行网络请求。一种不错的做法是使用  <code>withContext()</code>  来确保每个函数都是主线程安全的，这意味着，你可以从主线程调用每个函数。这样，调用方就从不需要考虑应该使用哪个线程来执行函数了</p>
<p>在前面的示例中， <code>fetchDocs()</code>  方法在主线程上执行；不过，它可以安全地调用  <code>get</code>  方法，这样会在后台执行网络请求。由于协程支持  <code>suspend</code>  和  <code>resume</code> ，因此  <code>withContext</code>  块完成后，主线程上的协程会立即根据  <code>get</code>  结果恢复</p>
<p>与基于回调的等效实现相比，<a href="https://link.juejin.cn?target=https%3A%2F%2FKotlin.github.io%2FKotlinx.coroutines%2FKotlinx-coroutines-core%2FKotlinx.coroutines%2Fwith-context.html"> <code>withContext()</code> </a> 不会增加额外的开销。此外在某些情况下，还可以优化  <code>withContext()</code>  调用，使其超越基于回调的等效实现。例如，如果某个函数对一个网络进行十次调用，你可以使用外部  <code>withContext()</code>  让 Kotlin 只切换一次线程。这样，即使网络库多次使用  <code>withContext()</code> ，它也会留在同一调度程序上，并避免切换线程。此外，Kotlin 还优化了  <code>Dispatchers.Default</code>  与  <code>Dispatchers.IO</code>  之间的切换，以尽可能避免线程切换</p>
<blockquote>
<p>利用一个使用线程池的调度程序（例如  <code>Dispatchers.IO</code>  或  <code>Dispatchers.Default</code> ）不能保证代码块一直在同一线程上从上到下执行。在某些情况下，Kotlin 协程在  <code>suspend</code>  和  <code>resume</code>  后可能会将执行工作移交给另一个线程。这意味着，对于整个  <code>withContext()</code>  块，线程局部变量可能并不指向同一个值</p>
</blockquote>
<h4 id="4-coroutinename"><a class="markdownIt-Anchor" href="#4-coroutinename">#</a> 4、CoroutineName</h4>
<p>CoroutineName 用于为协程指定一个名字，方便调试和定位问题</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking&lt;<span class="built_in">Unit</span>&gt;(CoroutineName(<span class="string">&quot;RunBlocking&quot;</span>)) &#123;</span><br><span class="line">    log(<span class="string">&quot;start&quot;</span>)</span><br><span class="line">    launch(CoroutineName(<span class="string">&quot;MainCoroutine&quot;</span>)) &#123;</span><br><span class="line">        launch(CoroutineName(<span class="string">&quot;Coroutine#A&quot;</span>)) &#123;</span><br><span class="line">            delay(<span class="number">400</span>)</span><br><span class="line">            log(<span class="string">&quot;launch A&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        launch(CoroutineName(<span class="string">&quot;Coroutine#B&quot;</span>)) &#123;</span><br><span class="line">            delay(<span class="number">300</span>)</span><br><span class="line">            log(<span class="string">&quot;launch B&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main <span class="meta">@RunBlocking</span>#<span class="number">1</span>] start</span><br><span class="line">[main <span class="meta">@Coroutine</span>#B#<span class="number">4</span>] launch B</span><br><span class="line">[main <span class="meta">@Coroutine</span>#A#<span class="number">3</span>] launch A</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="5-coroutineexceptionhandler"><a class="markdownIt-Anchor" href="#5-coroutineexceptionhandler">#</a> 5、CoroutineExceptionHandler</h4>
<p>在下文的异常处理会讲到</p>
<h4 id="6-组合上下文元素"><a class="markdownIt-Anchor" href="#6-组合上下文元素">#</a> 6、组合上下文元素</h4>
<p>有时我们需要为协程上下文定义多个元素，那就可以用  <code>+</code>  运算符。例如，我们可以同时为协程指定 Dispatcher 和 CoroutineName</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking&lt;<span class="built_in">Unit</span>&gt; &#123;</span><br><span class="line">    launch(Dispatchers.Default + CoroutineName(<span class="string">&quot;test&quot;</span>)) &#123;</span><br><span class="line">        log(<span class="string">&quot;Hello World&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span> <span class="meta">@test</span>#<span class="number">2</span>] Hello World</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>此外，由于 CoroutineContext 是由一组元素组成的，所以加号右侧的元素会覆盖加号左侧的元素，进而组成新创建的 CoroutineContext。比如， <code>(Dispatchers.Main, &quot;name&quot;) + (Dispatchers.IO) = (Dispatchers.IO, &quot;name&quot;)</code></p>
<h2 id="八-取消协程"><a class="markdownIt-Anchor" href="#八-取消协程">#</a> 八、取消协程</h2>
<p>如果用户退出某个启动了协程的 Activity/Fragment 的话，那么大部分情况下就应该取消所有协程</p>
<p><code>job.cancel()</code>  就用于取消协程， <code>job.join()</code>  用于阻塞等待协程运行结束。因为  <code>cancel()</code>  函数调用后会马上返回而不是等待协程结束后再返回，所以此时协程不一定就是已经停止运行了。如果需要确保协程结束运行后再执行后续代码，就需要调用  <code>join()</code>  方法来阻塞等待。也可以通过调用 Job 的扩展函数  <code>cancelAndJoin()</code>  来完成相同操作，它结合了  <code>cancel</code>  和  <code>join</code>  两个操作</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> job = launch &#123;</span><br><span class="line">        repeat(<span class="number">1000</span>) &#123; i -&gt;</span><br><span class="line">            log(<span class="string">&quot;job: I&#x27;m sleeping <span class="variable">$i</span> ...&quot;</span>)</span><br><span class="line">            delay(<span class="number">500L</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    delay(<span class="number">1300L</span>)</span><br><span class="line">    log(<span class="string">&quot;main: I&#x27;m tired of waiting!&quot;</span>)</span><br><span class="line">    job.cancel()</span><br><span class="line">    job.join()</span><br><span class="line">    log(<span class="string">&quot;main: Now I can quit.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] job: I<span class="string">&#x27;m sleeping 0 ...</span></span><br><span class="line"><span class="string">[main] job: I&#x27;</span>m sleeping <span class="number">1</span> ...</span><br><span class="line">[main] job: I<span class="string">&#x27;m sleeping 2 ...</span></span><br><span class="line"><span class="string">[main] main: I&#x27;</span>m tired of waiting!</span><br><span class="line">[main] main: Now I can quit.</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="1-协程可能无法取消"><a class="markdownIt-Anchor" href="#1-协程可能无法取消">#</a> 1、协程可能无法取消</h4>
<p>并不是所有协程都可以响应取消操作，协程的取消操作是需要协作 (cooperative) 完成的，协程必须协作才能取消。协程库中的所有挂起函数都是可取消的，它们在运行时会检查协程是否被取消了，并在取消时抛出 CancellationException 从而结束整个任务。但如果协程正在执行计算任务并且未检查是否已处于取消状态的话，就无法取消协程</p>
<p>所以即使以下代码主动取消了协程，协程也只会在完成既定循环后才结束运行，因为协程没有在每次循环前先进行检查，导致任务不受取消操作的影响</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> startTime = System.currentTimeMillis()</span><br><span class="line">    <span class="keyword">val</span> job = launch(Dispatchers.Default) &#123;</span><br><span class="line">        <span class="keyword">var</span> nextPrintTime = startTime</span><br><span class="line">        <span class="keyword">var</span> i = <span class="number">0</span></span><br><span class="line">        <span class="keyword">while</span> (i &lt; <span class="number">5</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (System.currentTimeMillis() &gt;= nextPrintTime) &#123;</span><br><span class="line">                log(<span class="string">&quot;job: I&#x27;m sleeping <span class="subst">$&#123;i++&#125;</span> ...&quot;</span>)</span><br><span class="line">                nextPrintTime += <span class="number">500L</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    delay(<span class="number">1300L</span>)</span><br><span class="line">    log(<span class="string">&quot;main: I&#x27;m tired of waiting!&quot;</span>)</span><br><span class="line">    job.cancelAndJoin()</span><br><span class="line">    log(<span class="string">&quot;main: Now I can quit.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span>] job: I<span class="string">&#x27;m sleeping 0 ...</span></span><br><span class="line"><span class="string">[DefaultDispatcher-worker-1] job: I&#x27;</span>m sleeping <span class="number">1</span> ...</span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span>] job: I<span class="string">&#x27;m sleeping 2 ...</span></span><br><span class="line"><span class="string">[main] main: I&#x27;</span>m tired of waiting!</span><br><span class="line">[DefaultDispatcher-worker-<span class="number">1</span>] job: I<span class="string">&#x27;m sleeping 3 ...</span></span><br><span class="line"><span class="string">[DefaultDispatcher-worker-1] job: I&#x27;</span>m sleeping <span class="number">4</span> ...</span><br><span class="line">[main] main: Now I can quit.</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>为了实现取消协程的目的，就需要为上述代码加上判断协程是否还处于可运行状态的逻辑，当不可运行时就主动退出协程。 <code>isActive</code>  是 CoroutineScope 的扩展属性，就用于判断协程是否还处于可运行状态</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> startTime = System.currentTimeMillis()</span><br><span class="line">    <span class="keyword">val</span> job = launch(Dispatchers.Default) &#123;</span><br><span class="line">        <span class="keyword">var</span> nextPrintTime = startTime</span><br><span class="line">        <span class="keyword">var</span> i = <span class="number">0</span></span><br><span class="line">        <span class="keyword">while</span> (i &lt; <span class="number">5</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (isActive) &#123;</span><br><span class="line">                <span class="keyword">if</span> (System.currentTimeMillis() &gt;= nextPrintTime) &#123;</span><br><span class="line">                    log(<span class="string">&quot;job: I&#x27;m sleeping <span class="subst">$&#123;i++&#125;</span> ...&quot;</span>)</span><br><span class="line">                    nextPrintTime += <span class="number">500L</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span><span class="symbol">@launch</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    delay(<span class="number">1300L</span>)</span><br><span class="line">    log(<span class="string">&quot;main: I&#x27;m tired of waiting!&quot;</span>)</span><br><span class="line">    job.cancelAndJoin()</span><br><span class="line">    log(<span class="string">&quot;main: Now I can quit.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>取消协程这个操作类似于在 Java 中调用 <code>Thread.interrupt()</code>  方法来向线程发起中断请求，这两个操作都不会强制停止协程和线程，外部只是相当于发起一个停止运行的请求，需要依靠协程和线程响应请求后主动停止运行。Kotlin 和 Java 之所以均没有提供一个可以直接强制停止协程或线程的方法，是因为这个操作可能会带来各种意想不到的情况。在停止协程和线程的时候，它们可能还持有着某些排他性资源（例如：锁，数据库链接），如果强制性地停止，它们持有的锁就会一直无法得到释放，导致其他协程和线程一直无法得到目标资源，最终可能导致线程死锁。所以 <code>Thread.stop()</code>  方法目前也是处于废弃状态，Java 官方并没有提供可靠的停止线程的方法</p>
<h4 id="2-用-finally-释放资源"><a class="markdownIt-Anchor" href="#2-用-finally-释放资源">#</a> 2、用 finally 释放资源</h4>
<p>可取消的挂起函数在取消时会抛出 CancellationException，可以依靠 <code>try {...} finally {...}</code>  或者 Kotlin 的  <code>use</code>  函数在取消协程后释放持有的资源</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> job = launch &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            repeat(<span class="number">1000</span>) &#123; i -&gt;</span><br><span class="line">                log(<span class="string">&quot;job: I&#x27;m sleeping <span class="variable">$i</span> ...&quot;</span>)</span><br><span class="line">                delay(<span class="number">500L</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (e: Throwable) &#123;</span><br><span class="line">            log(e.message)</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            log(<span class="string">&quot;job: I&#x27;m running finally&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    delay(<span class="number">1300L</span>)</span><br><span class="line">    log(<span class="string">&quot;main: I&#x27;m tired of waiting!&quot;</span>)</span><br><span class="line">    job.cancelAndJoin()</span><br><span class="line">    log(<span class="string">&quot;main: Now I can quit.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] job: I<span class="string">&#x27;m sleeping 0 ...</span></span><br><span class="line"><span class="string">[main] job: I&#x27;</span>m sleeping <span class="number">1</span> ...</span><br><span class="line">[main] job: I<span class="string">&#x27;m sleeping 2 ...</span></span><br><span class="line"><span class="string">[main] main: I&#x27;</span>m tired of waiting!</span><br><span class="line">[main] StandaloneCoroutine was cancelled</span><br><span class="line">[main] job: I<span class="string">&#x27;m running finally</span></span><br><span class="line"><span class="string">[main] main: Now I can quit.</span></span><br><span class="line"><span class="string"></span></span><br></pre></td></tr></table></figure></div>
<h4 id="3-noncancellable"><a class="markdownIt-Anchor" href="#3-noncancellable">#</a> 3、NonCancellable</h4>
<p>如果在上一个例子中的  <code>finally</code>  块中再调用挂起函数的话，将会导致抛出 CancellationException，因为此时协程已经被取消了。通常我们并不会遇到这种情况，因为常见的资源释放操作都是非阻塞的，且不涉及任何挂起函数。但在极少数情况下我们需要在取消的协程中再调用挂起函数，此时可以使用  <code>withContext</code>  函数和  <code>NonCancellable</code>  上下文将相应的代码包装在  <code>withContext(NonCancellable) {...}</code>  代码块中，NonCancellable 就用于创建一个无法取消的协程作用域</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    log(<span class="string">&quot;start&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> launchA = launch &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            repeat(<span class="number">5</span>) &#123;</span><br><span class="line">                delay(<span class="number">50</span>)</span><br><span class="line">                log(<span class="string">&quot;launchA-<span class="variable">$it</span>&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            delay(<span class="number">50</span>)</span><br><span class="line">            log(<span class="string">&quot;launchA isCompleted&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">val</span> launchB = launch &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            repeat(<span class="number">5</span>) &#123;</span><br><span class="line">                delay(<span class="number">50</span>)</span><br><span class="line">                log(<span class="string">&quot;launchB-<span class="variable">$it</span>&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            withContext(NonCancellable) &#123;</span><br><span class="line">                delay(<span class="number">50</span>)</span><br><span class="line">                log(<span class="string">&quot;launchB isCompleted&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//延时一百毫秒，保证两个协程都已经被启动了</span></span><br><span class="line">    delay(<span class="number">200</span>)</span><br><span class="line">    launchA.cancel()</span><br><span class="line">    launchB.cancel()</span><br><span class="line">    log(<span class="string">&quot;end&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] start</span><br><span class="line">[main] launchA-<span class="number">0</span></span><br><span class="line">[main] launchB-<span class="number">0</span></span><br><span class="line">[main] launchA-<span class="number">1</span></span><br><span class="line">[main] launchB-<span class="number">1</span></span><br><span class="line">[main] launchA-<span class="number">2</span></span><br><span class="line">[main] launchB-<span class="number">2</span></span><br><span class="line">[main] end</span><br><span class="line">[main] launchB isCompleted</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="4-父协程和子协程"><a class="markdownIt-Anchor" href="#4-父协程和子协程">#</a> 4、父协程和子协程</h4>
<p>当一个协程在另外一个协程的协程作用域中启动时，它将通过  <code>CoroutineScope.coroutineContext</code>  继承其上下文，新启动的协程就被称为子协程，子协程的 Job 将成为父协程 Job 的子 Job。父协程总是会等待其所有子协程都完成后才结束自身，所以父协程不必显式跟踪它启动的所有子协程，也不必使用  <code>Job.join</code>  在末尾等待子协程完成</p>
<p>所以虽然 parentJob 启动的三个子协程的延时时间各不相同，但它们最终都会打印出日志</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> parentJob = launch &#123;</span><br><span class="line">        repeat(<span class="number">3</span>) &#123; i -&gt;</span><br><span class="line">            launch &#123;</span><br><span class="line">                delay((i + <span class="number">1</span>) * <span class="number">200L</span>)</span><br><span class="line">                log(<span class="string">&quot;Coroutine <span class="variable">$i</span> is done&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        log(<span class="string">&quot;request: I&#x27;m done and I don&#x27;t explicitly join my children that are still active&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">2</span>] request: I<span class="string">&#x27;m done and I don&#x27;</span>t explicitly join my children that are still active</span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">3</span>] Coroutine <span class="number">0</span> <span class="keyword">is</span> done</span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">4</span>] Coroutine <span class="number">1</span> <span class="keyword">is</span> done</span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">5</span>] Coroutine <span class="number">2</span> <span class="keyword">is</span> done</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="5-传播取消操作"><a class="markdownIt-Anchor" href="#5-传播取消操作">#</a> 5、传播取消操作</h4>
<p>一般情况下，协程的取消操作会通过协程的层次结构来进行传播。如果取消父协程或者父协程抛出异常，那么子协程都会被取消。而如果子协程被取消，则不会影响同级协程和父协程，但如果子协程抛出异常则也会导致同级协程和父协程被取消</p>
<p>对于以下代码，子协程 jon1 被取消并不影响子协程 jon2 和父协程继续运行，但父协程被取消后子协程都会被递归取消</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> request = launch &#123;</span><br><span class="line">        <span class="keyword">val</span> job1 = launch &#123;</span><br><span class="line">            repeat(<span class="number">10</span>) &#123;</span><br><span class="line">                delay(<span class="number">300</span>)</span><br><span class="line">                log(<span class="string">&quot;job1: <span class="variable">$it</span>&quot;</span>)</span><br><span class="line">                <span class="keyword">if</span> (it == <span class="number">2</span>) &#123;</span><br><span class="line">                    log(<span class="string">&quot;job1 canceled&quot;</span>)</span><br><span class="line">                    cancel()</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">val</span> job2 = launch &#123;</span><br><span class="line">            repeat(<span class="number">10</span>) &#123;</span><br><span class="line">                delay(<span class="number">300</span>)</span><br><span class="line">                log(<span class="string">&quot;job2: <span class="variable">$it</span>&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    delay(<span class="number">1600</span>)</span><br><span class="line">    log(<span class="string">&quot;parent job canceled&quot;</span>)</span><br><span class="line">    request.cancel()</span><br><span class="line">    delay(<span class="number">1000</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">3</span>] job1: <span class="number">0</span></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">4</span>] job2: <span class="number">0</span></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">3</span>] job1: <span class="number">1</span></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">4</span>] job2: <span class="number">1</span></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">3</span>] job1: <span class="number">2</span></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">3</span>] job1 canceled</span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">4</span>] job2: <span class="number">2</span></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">4</span>] job2: <span class="number">3</span></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">4</span>] job2: <span class="number">4</span></span><br><span class="line">[main <span class="meta">@coroutine</span>#<span class="number">1</span>] parent job canceled</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="6-withtimeout"><a class="markdownIt-Anchor" href="#6-withtimeout">#</a> 6、withTimeout</h4>
<p><code>withTimeout</code>  函数用于指定协程的运行超时时间，如果超时则会抛出 TimeoutCancellationException，从而令协程结束运行</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    log(<span class="string">&quot;start&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> result = withTimeout(<span class="number">300</span>) &#123;</span><br><span class="line">        repeat(<span class="number">5</span>) &#123;</span><br><span class="line">            delay(<span class="number">100</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="number">200</span></span><br><span class="line">    &#125;</span><br><span class="line">    log(result)</span><br><span class="line">    log(<span class="string">&quot;end&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] start</span><br><span class="line">Exception <span class="keyword">in</span> thread <span class="string">&quot;main&quot;</span> kotlinx.coroutines.TimeoutCancellationException: Timed <span class="keyword">out</span> waiting <span class="keyword">for</span> <span class="number">300</span> ms</span><br><span class="line">	at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:<span class="number">186</span>)</span><br><span class="line">	at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:<span class="number">156</span>)</span><br><span class="line">	at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:<span class="number">497</span>)</span><br><span class="line">	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:<span class="number">274</span>)</span><br><span class="line">	at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:<span class="number">69</span>)</span><br><span class="line">	at java.lang.Thread.run(Thread.java:<span class="number">748</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p><code>withTimeout</code>  方法抛出的 TimeoutCancellationException 是 CancellationException 的子类，之前我们并未在输出日志上看到关于 CancellationException 这类异常的堆栈信息，这是因为对于一个已取消的协程来说，CancellationException 被认为是触发协程结束的正常原因。但对于 <code>withTimeout</code>  方法来说，抛出异常是其上报超时情况的一种手段，所以该异常不会被协程内部消化掉</p>
<p>如果不希望因为异常导致协程结束，可以改用 <code>withTimeoutOrNull</code>  方法，如果超时就会返回 null</p>
<h2 id="九-异常处理"><a class="markdownIt-Anchor" href="#九-异常处理">#</a> 九、异常处理</h2>
<p>当一个协程由于异常而运行失败时，它会传播这个异常并传递给它的父协程。接下来，父协程会进行下面几步操作：</p>
<ul>
<li>取消它自己的子级</li>
<li>取消它自己</li>
<li>将异常传播并传递给它的父级</li>
</ul>
<p>异常会到达层级的根部，而且当前 CoroutineScope 所启动的所有协程都会被取消，但协程并非都是一发现异常就执行以上流程，launch 和 async 在处理异常方面有着很大的差异</p>
<p>launch 将异常视为未捕获异常，类似于 Java 的 Thread.uncaughtExceptionHandler，当发现异常时就会马上抛出。async 期望最终是通过调用 await 来获取结果 (或者异常)，所以默认情况下它不会抛出异常。这意味着如果使用 async 启动新的协程，它会静默地将异常丢弃，直到调用  <code>async.await()</code>  才会得到目标值或者抛出存在的异常</p>
<p>例如，以下代码中 launchA 抛出的异常会先连锁导致 launchB 也被取消（抛出 JobCancellationException），然后再导致父协程 BlockingCoroutine 也被取消</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> launchA = launch &#123;</span><br><span class="line">        delay(<span class="number">1000</span>)</span><br><span class="line">        <span class="number">1</span> / <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">val</span> launchB = launch &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            delay(<span class="number">1300</span>)</span><br><span class="line">            log(<span class="string">&quot;launchB&quot;</span>)</span><br><span class="line">        &#125; <span class="keyword">catch</span> (e: CancellationException) &#123;</span><br><span class="line">            e.printStackTrace()</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    launchA.join()</span><br><span class="line">    launchB.join()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">kotlinx.coroutines.JobCancellationException: Parent job <span class="keyword">is</span> Cancelling; job=BlockingCoroutine&#123;Cancelling&#125;@5eb5c224</span><br><span class="line">Caused <span class="keyword">by</span>: java.lang.ArithmeticException: / <span class="keyword">by</span> zero</span><br><span class="line">	at coroutines.CoroutinesMainKt$main$<span class="number">1</span>$launchA$<span class="number">1.</span>invokeSuspend(CoroutinesMain.kt:<span class="number">11</span>)</span><br><span class="line">	···</span><br><span class="line">Exception <span class="keyword">in</span> thread <span class="string">&quot;main&quot;</span> java.lang.ArithmeticException: / <span class="keyword">by</span> zero</span><br><span class="line">	at coroutines.CoroutinesMainKt$main$<span class="number">1</span>$launchA$<span class="number">1.</span>invokeSuspend(CoroutinesMain.kt:<span class="number">11</span>)</span><br><span class="line">	···</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="1-coroutineexceptionhandler"><a class="markdownIt-Anchor" href="#1-coroutineexceptionhandler">#</a> 1、CoroutineExceptionHandler</h4>
<p>如果不想将所有的异常信息都打印到控制台上，那么可以使用 CoroutineExceptionHandler 作为协程的上下文元素之一，在这里进行自定义日志记录或异常处理，它类似于对线程使用 Thread.uncaughtExceptionHandler。但是，CoroutineExceptionHandler 只会在预计不会由用户处理的异常上调用，因此在 async 中使用它没有任何效果，当 async 内部发生了异常且没有捕获时，那么调用  <code>async.await()</code>  依然会导致应用崩溃</p>
<p>以下代码只会捕获到 launch 抛出的异常</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> handler = CoroutineExceptionHandler &#123; _, exception -&gt;</span><br><span class="line">        log(<span class="string">&quot;Caught <span class="variable">$exception</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">val</span> job = GlobalScope.launch(handler) &#123;</span><br><span class="line">        <span class="keyword">throw</span> AssertionError()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">val</span> deferred = GlobalScope.async(handler) &#123;</span><br><span class="line">        <span class="keyword">throw</span> ArithmeticException()</span><br><span class="line">    &#125;</span><br><span class="line">    joinAll(job, deferred)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[DefaultDispatcher-worker-<span class="number">2</span>] Caught java.lang.AssertionError</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="2-supervisorjob"><a class="markdownIt-Anchor" href="#2-supervisorjob">#</a> 2、SupervisorJob</h4>
<p>由于异常导致的取消在协程中是一种双向关系，会在整个协程层次结构中传播，但如果我们需要的是单向取消该怎么实现呢？</p>
<p>例如，假设在 Activity 中启动了多个协程，如果单个协程所代表的子任务失败了，此时并不一定需要连锁终止整个 Activity 内部的所有其它协程任务，即此时希望子协程的异常不会传播给同级协程和父协程。而当 Activity 退出后，父协程的异常（即 CancellationException）又应该连锁传播给所有子协程，终止所有子协程</p>
<p>可以使用 SupervisorJob 来实现上述效果，它类似于常规的 Job，唯一的区别就是取消操作只会向下传播，一个子协程的运行失败不会影响到其他子协程</p>
<p>例如，以下示例中 firstChild 抛出的异常不会导致 secondChild 被取消，但当 supervisor 被取消时 secondChild 也被同时取消了</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking &#123;</span><br><span class="line">    <span class="keyword">val</span> supervisor = SupervisorJob()</span><br><span class="line">    with(CoroutineScope(coroutineContext + supervisor)) &#123;</span><br><span class="line">        <span class="keyword">val</span> firstChild = launch(CoroutineExceptionHandler &#123; _, _ -&gt; &#125;) &#123;</span><br><span class="line">            log(<span class="string">&quot;First child is failing&quot;</span>)</span><br><span class="line">            <span class="keyword">throw</span> AssertionError(<span class="string">&quot;First child is cancelled&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">val</span> secondChild = launch &#123;</span><br><span class="line">            firstChild.join()</span><br><span class="line">            log(<span class="string">&quot;First child is cancelled: <span class="subst">$&#123;firstChild.isCancelled&#125;</span>, but second one is still active&quot;</span>)</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                delay(<span class="built_in">Long</span>.MAX_VALUE)</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                log(<span class="string">&quot;Second child is cancelled because supervisor is cancelled&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        firstChild.join()</span><br><span class="line">        log(<span class="string">&quot;Cancelling supervisor&quot;</span>)</span><br><span class="line">        <span class="comment">//取消所有协程</span></span><br><span class="line">        supervisor.cancel()</span><br><span class="line">        secondChild.join()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[main] First child <span class="keyword">is</span> failing</span><br><span class="line">[main] First child <span class="keyword">is</span> cancelled: <span class="literal">true</span>, but second one <span class="keyword">is</span> still active</span><br><span class="line">[main] Cancelling supervisor</span><br><span class="line">[main] Second child <span class="keyword">is</span> cancelled because supervisor <span class="keyword">is</span> cancelled</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>但是，如果异常没有被处理且 CoroutineContext 没有包含一个 CoroutineExceptionHandler 的话，异常会到达默认线程的 ExceptionHandler。在 JVM 中，异常会被打印在控制台；而在 Android 中，无论异常在那个 Dispatcher 中发生，都会直接导致应用崩溃。所以如果上述例子中移除了 firstChild 包含的 CoroutineExceptionHandler 的话，就会导致 Android 应用崩溃</p>
<p>💥 <strong>未被捕获的异常一定会被抛出，无论使用的是哪种 Job</strong></p>
<h2 id="十-android-ktx"><a class="markdownIt-Anchor" href="#十-android-ktx">#</a> 十、Android KTX</h2>
<p>Android KTX 是包含在 Android <a class="link"   href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.google.cn%2Fjetpack" >Jetpack <i class="fa-regular fa-arrow-up-right-from-square fa-sm"></i></a> 及其他 Android 库中的一组 Kotlin 扩展程序。KTX 扩展程序可以为 Jetpack、Android 平台及其他 API 提供简洁的惯用 Kotlin 代码。为此，这些扩展程序利用了多种 Kotlin 语言功能，其中就包括了对 Kotlin 协程的支持</p>
<h4 id="1-viewmodel-ktx"><a class="markdownIt-Anchor" href="#1-viewmodel-ktx">#</a> 1、ViewModel KTX</h4>
<p>ViewModel KTX 库提供了一个  <code>viewModelScope</code> ，用于在 ViewModel 启动协程，该作用域的生命周期和 ViewModel 相等，当 ViewModel 回调了  <code>onCleared()</code>  方法后会自动取消所有当前 ViewModel 中的所有协程</p>
<p>引入依赖：</p>
<div class="highlight-container" data-rel="Groovy"><figure class="iseeu highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">    implementation <span class="string">&quot;androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>例如，以下  <code>fetchDocs()</code>  方法内就依靠  <code>viewModelScope</code>  启动了一个协程，用于在后台线程发起网络请求</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyViewModel</span> : <span class="type">ViewModel</span>() &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">fetchDocs</span><span class="params">()</span></span> &#123;</span><br><span class="line">        viewModelScope.launch &#123;</span><br><span class="line">            <span class="keyword">val</span> result = <span class="keyword">get</span>(<span class="string">&quot;https://developer.android.com&quot;</span>)</span><br><span class="line">            show(result)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">get</span><span class="params">(url: <span class="type">String</span>)</span></span> = withContext(Dispatchers.IO) &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="2-lifecycle-ktx"><a class="markdownIt-Anchor" href="#2-lifecycle-ktx">#</a> 2、Lifecycle KTX</h4>
<p>Lifecycle KTX 为每个 <a href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.google.cn%2Ftopic%2Flibraries%2Farchitecture%2Flifecycle"> <code>Lifecycle</code> </a> 对象定义了一个  <code>LifecycleScope</code> ，该作用域具有生命周期安全的保障，在此范围内启动的协程会在  <code>Lifecycle</code>  被销毁时同时取消，可以使用  <code>lifecycle.coroutineScope</code>  或  <code>lifecycleOwner.lifecycleScope</code>  属性来拿到该 CoroutineScope</p>
<p>引入依赖：</p>
<div class="highlight-container" data-rel="Groovy"><figure class="iseeu highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">    implementation <span class="string">&quot;androidx.lifecycle:lifecycle-runtime-ktx:2.2.0&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>以下示例演示了如何使用  <code>lifecycleOwner.lifecycleScope</code>  异步创建预计算文本：</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyFragment</span>: <span class="type">Fragment</span>() &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onViewCreated</span><span class="params">(view: <span class="type">View</span>, savedInstanceState: <span class="type">Bundle</span>?)</span></span> &#123;</span><br><span class="line">        <span class="keyword">super</span>.onViewCreated(view, savedInstanceState)</span><br><span class="line">        viewLifecycleOwner.lifecycleScope.launch &#123;</span><br><span class="line">            <span class="keyword">val</span> params = TextViewCompat.getTextMetricsParams(textView)</span><br><span class="line">            <span class="keyword">val</span> precomputedText = withContext(Dispatchers.Default) &#123;</span><br><span class="line">                PrecomputedTextCompat.create(longTextContent, params)</span><br><span class="line">            &#125;</span><br><span class="line">            TextViewCompat.setPrecomputedText(textView, precomputedText)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<h4 id="3-livedata-ktx"><a class="markdownIt-Anchor" href="#3-livedata-ktx">#</a> 3、LiveData KTX</h4>
<p>使用 LiveData 时，你可能需要异步计算值。例如，你可能需要检索用户的偏好设置并将其传送给界面。在这些情况下，LiveData KTX 提供了一个  <code>liveData</code>  构建器函数，该函数会调用 suspend 函数并将结果赋值给 LiveData</p>
<p>引入依赖：</p>
<div class="highlight-container" data-rel="Groovy"><figure class="iseeu highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">    implementation <span class="string">&quot;androidx.lifecycle:lifecycle-livedata-ktx:2.2.0&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div>
<p>在以下示例中， <code>loadUser()</code>  是在其他地方声明的 suspend 函数。 你可以使用  <code>liveData</code>  构建器函数异步调用  <code>loadUser()</code> ，然后使用  <code>emit()</code>  来发出结果：</p>
<div class="highlight-container" data-rel="Kotlin"><figure class="iseeu highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> user: LiveData&lt;User&gt; = liveData &#123;</span><br><span class="line">    <span class="keyword">val</span> <span class="keyword">data</span> = database.loadUser() <span class="comment">// loadUser is a suspend function.</span></span><br><span class="line">    emit(<span class="keyword">data</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

            </div>

            
                <div class="post-copyright-info">
                    <div class="article-copyright-info-container">
    <ul>
        <li><strong>标题:</strong> Kotlin协程</li>
        <li><strong>作者:</strong> meteor</li>
        <li><strong>创建于:</strong> 2023-06-05 16:53:30</li>
        
            <li>
                <strong>更新于:</strong> 2023-06-05 00:53:56
            </li>
        
        <li>
            <strong>链接:</strong> https://gitee.com/du-jinliang/2023/06/06/Kotlin协程/
        </li>
        <li>
            <strong>版权声明:</strong> 本文章采用 <a class="license" target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">CC BY-NC-SA 4.0</a> 进行许可。
        </li>
    </ul>
</div>

                </div>
            

            
                <ul class="post-tags-box">
                    
                        <li class="tag-item">
                            <a href="/tags/%E5%8D%8F%E7%A8%8B/">#协程</a>&nbsp;
                        </li>
                    
                </ul>
            

            

            
                <div class="article-nav">
                    
                        <div class="article-prev">
                            <a class="prev"
                            rel="prev"
                            href="/2023/06/06/Mybatis%E6%89%B9%E9%87%8F%E6%8F%92%E5%85%A5%E6%95%B0%E6%8D%AE%E7%9A%84%E4%B8%89%E7%A7%8D%E6%96%B9%E5%BC%8F%E6%95%88%E7%8E%87%E5%AF%B9%E6%AF%94/"
                            >
                                <span class="left arrow-icon flex-center">
                                    <i class="fa-solid fa-chevron-left"></i>
                                </span>
                                <span class="title flex-center">
                                    <span class="post-nav-title-item">Mybatis批量插入数据的三种方式效率对比</span>
                                    <span class="post-nav-item">上一篇</span>
                                </span>
                            </a>
                        </div>
                    
                    
                        <div class="article-next">
                            <a class="next"
                            rel="next"
                            href="/2023/06/06/Kotlin/"
                            >
                                <span class="title flex-center">
                                    <span class="post-nav-title-item">Kotlin</span>
                                    <span class="post-nav-item">下一篇</span>
                                </span>
                                <span class="right arrow-icon flex-center">
                                    <i class="fa-solid fa-chevron-right"></i>
                                </span>
                            </a>
                        </div>
                    
                </div>
            


            
                <div class="comment-container">
                    <div class="comments-container">
    <div id="comment-anchor"></div>
    <div class="comment-area-title">
        <i class="fa-solid fa-comments"></i>&nbsp;评论
    </div>
    

        
            
    <div id="gitalk-container"></div>
    <script data-pjax
            src="//cdn.jsdelivr.net/npm/gitalk/dist/gitalk.min.js"></script>
    <script data-pjax>

        function loadGitalk() {
            let __gitalk__pathname = decodeURI(location.pathname);
            const __gitalk__pathnameLength = __gitalk__pathname.length;
            const __gitalk__pathnameMaxLength = 50;
            if (__gitalk__pathnameLength > __gitalk__pathnameMaxLength) {
                __gitalk__pathname = __gitalk__pathname.substring(0, __gitalk__pathnameMaxLength - 3) + '...';
            }

            try {
                Gitalk && new Gitalk({
                    clientID: '55bad54a77b7e60ad62d',
                    clientSecret: '1031c81500c6be06e338087cb7b713f2d0201b46',
                    repo: 'wait-you.github.io',
                    owner: 'wait-you',
                    admin: ['wait-you'],
                    id: 'comment',
                    language: 'zh-CN'
                }).render('gitalk-container');

            } catch (e) {
                window.Gitalk = null;
            }
        }

        if ('true') {
            const loadGitalkTimeout = setTimeout(() => {
                loadGitalk();
                clearTimeout(loadGitalkTimeout);
            }, 1000);
        } else {
            window.addEventListener('DOMContentLoaded', loadGitalk);
        }
    </script>



        
    
</div>

                </div>
            
        </div>

        
            <div class="toc-content-container">
                <div class="post-toc-wrap">
    <div class="post-toc">
        <div class="toc-title">此页目录</div>
        <div class="page-title">Kotlin协程</div>
        <ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#kotlin%E5%8D%8F%E7%A8%8B"><span class="nav-text"> Kotlin 协程</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%80-kotlin-%E5%8D%8F%E7%A8%8B"><span class="nav-text"> 一、Kotlin 协程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%8C-%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%8D%8F%E7%A8%8B"><span class="nav-text"> 二、第一个协程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%89-suspend-function"><span class="nav-text"> 三、suspend function</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%9B%9B-suspend-function-%E7%9A%84%E6%8C%82%E8%B5%B7%E4%B8%8E%E6%81%A2%E5%A4%8D"><span class="nav-text"> 四、suspend function 的挂起与恢复</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%94-coroutinescope"><span class="nav-text"> 五、CoroutineScope</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%85%AD-coroutinebuilder"><span class="nav-text"> 六、CoroutineBuilder</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%83-coroutinecontext"><span class="nav-text"> 七、CoroutineContext</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%85%AB-%E5%8F%96%E6%B6%88%E5%8D%8F%E7%A8%8B"><span class="nav-text"> 八、取消协程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B9%9D-%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86"><span class="nav-text"> 九、异常处理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%8D%81-android-ktx"><span class="nav-text"> 十、Android KTX</span></a></li></ol></li></ol>

    </div>
</div>
            </div>
        
    </div>
</div>


                

            </div>
            
            

        </div>

        <div class="main-content-footer">
            <footer class="footer">
    <div class="info-container">
        <div class="copyright-info">
            &copy;
            
              <span>2022</span>
              -
            
            2023&nbsp;&nbsp;<i class="fa-solid fa-heart fa-beat" style="--fa-animation-duration: 0.5s; color: #f54545"></i>&nbsp;&nbsp;<a href="/">meteor</a>
        </div>
        
            <script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
            <div class="website-count info-item">
                
                    <span id="busuanzi_container_site_uv" class="busuanzi_container_site_uv">
                        访问人数&nbsp;<span id="busuanzi_value_site_uv" class="busuanzi_value_site_uv"></span>
                    </span>
                
                
                    <span id="busuanzi_container_site_pv" class="busuanzi_container_site_pv">
                        总访问量&nbsp;<span id="busuanzi_value_site_pv" class="busuanzi_value_site_pv"></span>
                    </span>
                
            </div>
        
        <div class="theme-info info-item">
            <span class="powered-by-container">由 <?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1rem" height="1rem" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"><path fill="#0E83CD" d="M256.4,25.8l-200,115.5L56,371.5l199.6,114.7l200-115.5l0.4-230.2L256.4,25.8z M349,354.6l-18.4,10.7l-18.6-11V275H200v79.6l-18.4,10.7l-18.6-11v-197l18.5-10.6l18.5,10.8V237h112v-79.6l18.5-10.6l18.5,10.8V354.6z"/></svg><a target="_blank" href="https://hexo.io">Hexo</a> 驱动</span>
                <br>
            <span class="theme-version-container">主题&nbsp;<a class="theme-version" target="_blank" href="https://github.com/EvanNotFound/hexo-theme-redefine">Redefine v2.1.4</a>
        </div>
        
            <div class="icp-info info-item"><a target="_blank" rel="nofollow" href="
                
                    beian.miit.gov.cn
                
                ">冀ICP备20010108号</a></div>
        
        
        
            <div id="start_div" style="display:none">
                2022/8/17 11:45:14
            </div>
            <div>
                博客已运行 <span class="odometer" id="runtime_days" ></span> 天 <span class="odometer" id="runtime_hours"></span> 小时 <span class="odometer" id="runtime_minutes"></span> 分钟 <span class="odometer" id="runtime_seconds"></span> 秒
            </div>
        
        
        
            <script async data-pjax>
                try {
                    function odometer_init() {
                    const elements = document.querySelectorAll('.odometer');
                    elements.forEach(el => {
                        new Odometer({
                            el,
                            format: '( ddd).dd',
                            duration: 200
                        });
                    });
                    }
                    odometer_init();
                } catch (error) {}
            </script>
        
        
        
    </div>  
</footer>
        </div>
    </div>

    
        <div class="post-tools">
            <div class="post-tools-container">
    <ul class="article-tools-list">
        <!-- TOC aside toggle -->
        
            <li class="right-bottom-tools page-aside-toggle">
                <i class="fa-regular fa-outdent"></i>
            </li>
        

        <!-- go comment -->
        
            <li class="go-comment">
                <i class="fa-regular fa-comments"></i>
            </li>
        
    </ul>
</div>

        </div>
    

    <div class="right-side-tools-container">
        <div class="side-tools-container">
    <ul class="hidden-tools-list">
        <li class="right-bottom-tools tool-font-adjust-plus flex-center">
            <i class="fa-regular fa-magnifying-glass-plus"></i>
        </li>

        <li class="right-bottom-tools tool-font-adjust-minus flex-center">
            <i class="fa-regular fa-magnifying-glass-minus"></i>
        </li>

        <li class="right-bottom-tools tool-expand-width flex-center">
            <i class="fa-regular fa-expand"></i>
        </li>

        <li class="right-bottom-tools tool-dark-light-toggle flex-center">
            <i class="fa-regular fa-moon"></i>
        </li>

        <!-- rss -->
        

        

        <li class="right-bottom-tools tool-scroll-to-bottom flex-center">
            <i class="fa-regular fa-arrow-down"></i>
        </li>
    </ul>

    <ul class="visible-tools-list">
        <li class="right-bottom-tools toggle-tools-list flex-center">
            <i class="fa-regular fa-cog fa-spin"></i>
        </li>
        
            <li class="right-bottom-tools tool-scroll-to-top flex-center">
                <i class="arrow-up fas fa-arrow-up"></i>
                <span class="percent"></span>
            </li>
        
        
    </ul>
</div>

    </div>

    <div class="image-viewer-container">
    <img src="">
</div>


    


</main>




<script src="/js/utils.js"></script>

<script src="/js/main.js"></script>

<script src="/js/layouts/navbarShrink.js"></script>

<script src="/js/tools/scrollTopBottom.js"></script>

<script src="/js/tools/lightDarkSwitch.js"></script>





    
<script src="/js/tools/codeBlock.js"></script>




    
<script src="/js/layouts/lazyload.js"></script>




    
<script src="/js/tools/runtime.js"></script>

    
<script src="/js/layouts/odometer.min.js"></script>

    
<link rel="stylesheet" href="/assets/odometer-theme-minimal.css">




  
<script src="/js/libs/Typed.min.js"></script>

  
<script src="/js/plugins/typed.js"></script>







<div class="post-scripts pjax">
    
        
<script src="/js/tools/tocToggle.js"></script>

<script src="/js/libs/anime.min.js"></script>

<script src="/js/layouts/toc.js"></script>

<script src="/js/plugins/tabs.js"></script>

    
</div>


    
<script src="/js/libs/pjax.min.js"></script>

<script>
    window.addEventListener('DOMContentLoaded', () => {
        window.pjax = new Pjax({
            selectors: [
                'head title',
                '.page-container',
                '.pjax',
            ],
            history: true,
            debug: false,
            cacheBust: false,
            timeout: 0,
            analytics: false,
            currentUrlFullReload: false,
            scrollRestoration: false,
            // scrollTo: true,
        });

        document.addEventListener('pjax:send', () => {
            Global.utils.pjaxProgressBarStart();
        });

        document.addEventListener('pjax:complete', () => {
            Global.utils.pjaxProgressBarEnd();
            window.pjax.executeScripts(document.querySelectorAll('script[data-pjax], .pjax script'));
            Global.refresh();
        });
    });
</script>




</body>
</html>
